<\/div>'),r=n(this.suggestionsContainer),r.appendTo("body").width(this.options.width);r.on("mouseover",u,function(){i.activate(n(this).data("index"))});r.on("click",u,function(){i.select(n(this).data("index"))});if(this.fixPosition(),window.opera)this.el.on("keypress",function(n){i.onKeyPress(n)});else this.el.on("keydown",function(n){i.onKeyPress(n)});this.el.on("keyup",function(n){i.onKeyUp(n)});this.el.on("blur",function(){i.onBlur()});this.el.on("focus",function(){i.fixPosition()})},onBlur:function(){this.enableKillerFn()},setOptions:function(t){var r=this.options;i.extend(r,t),this.isLocal=n.isArray(r.lookup),this.isLocal&&typeof r.lookup[0]=="string"&&(r.lookup=n.map(r.lookup,function(n){return{value:n,data:null}})),n(this.suggestionsContainer).css({"max-height":r.maxHeight+"px",width:r.width,"z-index":r.zIndex})},clearCache:function(){this.cachedResponse=[],this.badQueries=[]},disable:function(){this.disabled=!0},enable:function(){this.disabled=!1},fixPosition:function(){var t=this.el.offset();n(this.suggestionsContainer).css({top:t.top+this.el.outerHeight()+"px",left:t.left+"px"})},enableKillerFn:function(){var t=this;n(document).on("click",t.killerFn)},disableKillerFn:function(){var t=this;n(document).off("click",t.killerFn)},killSuggestions:function(){var n=this;n.stopKillSuggestions(),n.intervalId=window.setInterval(function(){n.hide(),n.stopKillSuggestions()},300)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},onKeyPress:function(n){if(!this.disabled&&!this.visible&&n.keyCode===40&&this.currentValue){this.suggest();return}if(!this.disabled&&this.visible){switch(n.keyCode){case 27:this.el.val(this.currentValue),this.hide();break;case 9:case 13:if(this.selectedIndex===-1){this.hide();return}if(this.select(this.selectedIndex),n.keyCode===9)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}n.stopImmediatePropagation(),n.preventDefault()}},onKeyUp:function(n){if(!this.disabled){switch(n.keyCode){case 38:case 40:return}if(clearInterval(this.onChangeInterval),this.currentValue!==this.el.val())if(this.options.deferRequestBy>0){var t=this;this.onChangeInterval=setInterval(function(){t.onValueChange()},this.options.deferRequestBy)}else this.onValueChange()}},onValueChange:function(){clearInterval(this.onChangeInterval),this.currentValue=this.element.value;var n=this.getQuery(this.currentValue);if(this.selectedIndex=-1,this.ignoreValueChange){this.ignoreValueChange=!1;return}n===""||n.length
'+e(i,o)+"<\/div>";r.html(u).show(),this.visible=!0,this.selectedIndex=0,r.children().first().addClass(h)},processResponse:function(t){var i=n.parseJSON(t);typeof i.suggestions[0]=="string"&&(i.suggestions=n.map(i.suggestions,function(n){return{value:n,data:null}})),this.options.noCache||(this.cachedResponse[i.query]=i,i.suggestions.length===0&&this.badQueries.push(i.query)),i.query===this.getQuery(this.currentValue)&&(this.suggestions=i.suggestions,this.suggest())},activate:function(t){var i,r=this.classes.selected,u=n(this.suggestionsContainer),f=u.children();return(u.children("."+r).removeClass(r),this.selectedIndex=t,this.selectedIndex!==-1&&f.length>this.selectedIndex)?(i=f.get(this.selectedIndex),n(i).addClass(r),i):null},select:function(n){var t=this.suggestions[n];if(t){this.el.val(t),this.ignoreValueChange=!0,this.hide();this.onSelect(n)}},change:function(t){var r,i=this,f=this.suggestions[t],u;f&&(u=i.suggestions[t],i.el.val(i.getValue(u.value)),r=i.options.onChange,n.isFunction(r)&&r(u,i.el))},moveUp:function(){if(this.selectedIndex!==-1){if(this.selectedIndex===0){n(this.suggestionsContainer).children().first().removeClass(this.classes.selected),this.selectedIndex=-1,this.el.val(this.currentValue);return}this.adjustScroll(this.selectedIndex-1)}},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(t){var u=this.activate(t),i,r,f,e=25;u&&(i=u.offsetTop,r=n(this.suggestionsContainer).scrollTop(),f=r+this.options.maxHeight-e,if&&n(this.suggestionsContainer).scrollTop(i-this.options.maxHeight+e),this.el.val(this.getValue(this.suggestions[t].value)))},onSelect:function(t){var i=this,r=i.options.onSelect,u=i.suggestions[t];i.el.val(i.getValue(u.value)),n.isFunction(r)&&r.call(i.element,u)},getValue:function(n){var r=this,u=r.options.delimiter,t,i;return u?(t=r.currentValue,i=t.split(u),i.length===1)?n:t.substr(0,t.length-i[i.length-1].length)+n:n}},n.fn.autocomplete=function(i,r){return this.each(function(){var f="autocomplete",e=n(this),u;typeof i=="string"?(u=e.data(f),typeof u[i]=="function"&&u[i](r)):(u=new t(this,i),e.data(f,u))})}})(jQuery);
+//@ sourceMappingURL=jquery.autocomplete.min.js.map
\ No newline at end of file
diff --git a/dist/jquery.autocomplete.min.js.map b/dist/jquery.autocomplete.min.js.map
new file mode 100644
index 0000000..1f9f855
--- /dev/null
+++ b/dist/jquery.autocomplete.min.js.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"jquery.autocomplete.min.js",
+"lineCount":1,
+"mappings":"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",
+"sources":["jquery.autocomplete.js"],
+"names":["join","$","Autocomplete","el","options","that","defaults","formatResult","element","suggestions","badQueries","selectedIndex","currentValue","value","intervalId","cachedResponse","onChangeInterval","onChange","ignoreValueChange","isLocal","suggestionsContainer","classes","initialize","setOptions","utils","extend","target","source","addEvent","eventType","handler","addEventListener","attachEvent","Error","removeEvent","removeEventListener","detachEvent","createNode","html","div","document","createElement","innerHTML","firstChild","Autocomplete.formatResult","suggestion","reEscape","RegExp","pattern","replace","prototype","suggestionSelector","container","setAttribute","killerFn",".killerFn","e","closest","length","killSuggestions","disableKillerFn","width","outerWidth","appendTo","on","activate","data","select","fixPosition","window","opera","onKeyPress","onKeyUp","onBlur","enableKillerFn","suppliedOptions","isArray","lookup","map","css","maxHeight","zIndex","clearCache","disable","disabled","enable","offset","top","outerHeight","left","off","stopKillSuggestions","setInterval","hide","clearInterval","visible","keyCode","suggest","val","moveUp","moveDown","stopImmediatePropagation","preventDefault","deferRequestBy","me","onValueChange","q","getQuery","minChars","getSuggestions","delimiter","parts","split","trim","getSuggestionsLocal","toLowerCase","grep","indexOf","response","isBadQuery","params","query","ajax","serviceUrl","type","done","txt","processResponse","i","len","formatResults","className","classSelected","selected","show","children","first","addClass","text","parseJSON","noCache","push","index","activeItem","removeClass","get","selectedValue","onSelect","change","getValue","isFunction","adjustScroll","offsetTop","upperBound","lowerBound","heightDelta","scrollTop","onSelectCallback","call","substr","fn","autocomplete","$.fn.autocomplete","args","each","dataKey","inputElement","instance","jQuery"]
+}
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..11b3ff1
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,21 @@
+Copyright 2012 DevBridge and other contributors
+http://www.devbridge.com/projects/autocomplete/jquery/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/readme.md b/readme.md
index 7eb5fc8..1a8ab42 100644
--- a/readme.md
+++ b/readme.md
@@ -1 +1,63 @@
-# jQuery Autocomplete
+#Ajax AutoComplete for jQuery
+
+Ajax Autocomplete for jQuery allows you to easily create
+autocomplete/autosuggest boxes for text input fields.
+
+
+##Usage
+
+Html:
+
+
+
+Ajax lookup:
+
+ $('#autocomplete').autocomplete({
+ serviceUrl: '/autocomplete/countries',
+ onSelect: function (suggestion) {
+ status.html('You selected: ' + suggestion);
+ }
+ });
+
+Local lookup (no ajax):
+
+ $('#autocomplete').autocomplete({
+ lookup: countries,
+ onSelect: function (suggestion) {
+ status.html('You selected: ' + suggestion);
+ }
+ });
+
+##Response Format
+
+Response from the server must be JSON formatted following JavaScript object:
+
+ {
+ query: "Unit",
+ suggestions: [
+ { value: "United Arab Emirates", data: "AE" },
+ { value: "United Kingdom", data: "UK" },
+ { value: "United States", data: "US" }
+ ]
+ }
+
+Data can be any value or object. Data object is passed to formatResults function and onSelect callback. Alternatively, if there is no data you can supply just a string array for suggestions:
+
+ {
+ query: "Unit",
+ suggestions: ["United Arab Emirates", "United Kingdom", "United States"]
+ }
+
+Important: query value must match original value in the input field, otherwise suggestions will not be displayed.
+
+##License
+
+Ajax Autocomplete for jQuery is freely distributable under the
+terms of an MIT-style [license](https://github.com/devbridge/jQuery-Autocomplete/dist/license.txt).
+
+Copyright notice and permission notice shall be included in all
+copies or substantial portions of the Software.
+
+##Authors
+
+Tomas Kirda / [@tkirda](https://twitter.com/tkirda)
diff --git a/scripts/demo.js b/scripts/demo.js
index 7635395..5f2d035 100644
--- a/scripts/demo.js
+++ b/scripts/demo.js
@@ -4,20 +4,50 @@
$(function () {
'use strict';
+ // Load countries then initialize plugin:
$.ajax({
url: 'content/countries.txt',
dataType: 'json'
- }).done(function (data) {
- var status = $('#selection'),
- countries = $.map(data, function (value) {
- return value;
- });
+ }).done(function (source) {
- $('#query').autocomplete({
- lookup: countries,
- onSelect: function (suggestion) {
- status.html('You selected: ' + suggestion);
+ var countriesArray = $.map(source, function (value, key) { return { value: value, data: key }; }),
+ countries = $.map(source, function (value) { return value; });
+
+ // Setup jQuery ajax mock:
+ $.mockjax({
+ url: '*',
+ responseTime: 200,
+ response: function (settings) {
+ var query = settings.data.query,
+ queryLowerCase = query.toLowerCase(),
+ suggestions = $.grep(countries, function(country) {
+ return country.toLowerCase().indexOf(queryLowerCase) !== -1;
+ }),
+ response = {
+ query: query,
+ suggestions: suggestions
+ };
+
+ this.responseText = JSON.stringify(response);
}
});
+
+ // Initialize ajax autocomplete:
+ $('#autocomplete-ajax').autocomplete({
+ serviceUrl: '/autosuggest/service/url',
+ onSelect: function(suggestion) {
+ $('#selction-ajax').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
+ }
+ });
+
+ // Initialize autocomplete with local lookup:
+ $('#autocomplete').autocomplete({
+ lookup: countriesArray,
+ onSelect: function (suggestion) {
+ $('#selection').html('You selected: ' + suggestion.value + ', ' + suggestion.data);
+ }
+ });
+
});
+
});
\ No newline at end of file
diff --git a/scripts/jquery.mockjax.js b/scripts/jquery.mockjax.js
new file mode 100644
index 0000000..3930904
--- /dev/null
+++ b/scripts/jquery.mockjax.js
@@ -0,0 +1,523 @@
+/*!
+ * MockJax - jQuery Plugin to Mock Ajax requests
+ *
+ * Version: 1.5.1
+ * Released:
+ * Home: http://github.com/appendto/jquery-mockjax
+ * Author: Jonathan Sharp (http://jdsharp.com)
+ * License: MIT,GPL
+ *
+ * Copyright (c) 2011 appendTo LLC.
+ * Dual licensed under the MIT or GPL licenses.
+ * http://appendto.com/open-source-licenses
+ */
+(function($) {
+ var _ajax = $.ajax,
+ mockHandlers = [],
+ 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('Error: ' + $(xmlDoc).text() );
+ }
+ } else {
+ throw('Unable to parse XML');
+ }
+ } catch( e ) {
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+ $(document).trigger('xmlParseError', [ msg ]);
+ return undefined;
+ }
+ return xmlDoc;
+ }
+
+ // Trigger a jQuery event
+ function trigger(s, type, args) {
+ (s.context ? $(s.context) : $.event).trigger(type, args);
+ }
+
+ // 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 = false;
+ // 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, v) {
+ if ( live[k] === undefined ) {
+ identical = false;
+ return identical;
+ } else {
+ identical = true;
+ if ( typeof live[k] == 'object' ) {
+ return isMockDataEqual(mock[k], live[k]);
+ } else {
+ if ( $.isFunction( mock[k].test ) ) {
+ identical = mock[k].test(live[k]);
+ } else {
+ identical = ( mock[k] == live[k] );
+ }
+ return identical;
+ }
+ }
+ });
+
+ return identical;
+ }
+
+ // 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('*', '.+')).test(requestSettings.url)) {
+ return null;
+ }
+ }
+
+ // Inspect the data submitted in the request (either POST body or GET query string)
+ if ( handler.data && requestSettings.data ) {
+ if ( !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;
+ }
+
+ // If logging is enabled, log the mock to the console
+ function logMock( mockHandler, requestSettings ) {
+ var c = $.extend({}, $.mockjaxSettings, mockHandler);
+ if ( c.log && $.isFunction(c.log) ) {
+ c.log('MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url, $.extend({}, requestSettings));
+ }
+ }
+
+ // 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 = 4;
+
+ // We have an executable function, call it to give
+ // the mock handler a chance to update it's data
+ if ( $.isFunction(mockHandler.response) ) {
+ mockHandler.response(origSettings);
+ }
+ // 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);
+ } else {
+ this.responseXML = mockHandler.responseXML;
+ }
+ } 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 < 1.4 doesn't have onreadystate change for xhr
+ if ( $.isFunction(this.onreadystatechange) ) {
+ if( mockHandler.isTimeout) {
+ this.status = -1;
+ }
+ this.onreadystatechange( mockHandler.isTimeout ? 'timeout' : undefined );
+ } else if ( mockHandler.isTimeout ) {
+ // Fix for 1.3.2 timeout to keep success from firing.
+ this.status = -1;
+ }
+ }).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, txt) {
+ mockHandler.responseXML = xhr.responseXML;
+ mockHandler.responseText = xhr.responseText;
+ mockHandler.status = xhr.status;
+ mockHandler.statusText = xhr.statusText;
+ this.responseTimer = setTimeout(process, mockHandler.responseTime || 0);
+ }
+ });
+ } else {
+ // type == 'POST' || 'GET' || 'DELETE'
+ if ( requestSettings.async === false ) {
+ // TODO: Blocking delay
+ process();
+ } else {
+ this.responseTimer = setTimeout(process, 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 ( 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) {
+ mockHandler.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 = '';
+ $.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);
+
+ // 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
+ jsonpSuccess( requestSettings, mockHandler );
+ jsonpComplete( requestSettings, mockHandler );
+
+ // 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 ) {
+ 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, mockHandler );
+ jsonpComplete( requestSettings, 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, mockHandler) {
+ // If a local callback was specified, fire it and pass it the data
+ if ( requestSettings.success ) {
+ requestSettings.success.call( callbackContext, ( mockHandler.response ? mockHandler.response.toString() : mockHandler.responseText || ''), status, {} );
+ }
+
+ // Fire the global callback
+ if ( requestSettings.global ) {
+ trigger(requestSettings, "ajaxSuccess", [{}, requestSettings] );
+ }
+ }
+
+ // The JSONP request was completed
+ function jsonpComplete(requestSettings, mockHandler) {
+ // Process result
+ if ( requestSettings.complete ) {
+ requestSettings.complete.call( callbackContext, {} , status );
+ }
+
+ // The request was completed
+ if ( requestSettings.global ) {
+ 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;
+
+ // 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.url = url;
+ }
+
+ // Extend the original settings for the request
+ requestSettings = $.extend(true, {}, $.ajaxSettings, origSettings);
+
+ // 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;
+ }
+
+ // Handle console logging
+ logMock( mockHandler, requestSettings );
+
+
+ if ( requestSettings.dataType === "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;
+
+ (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, trigger a normal request
+ return _ajax.apply($, [origSettings]);
+ }
+
+
+ // Public
+
+ $.extend({
+ ajax: handleAjax
+ });
+
+ $.mockjaxSettings = {
+ //url: null,
+ //type: 'GET',
+ log: function( msg ) {
+ if ( window[ 'console' ] && window.console.log ) {
+ window.console.log.apply( console, arguments );
+ }
+ },
+ status: 200,
+ statusText: "OK",
+ responseTime: 500,
+ isTimeout: 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;
+ };
+ $.mockjaxClear = function(i) {
+ if ( arguments.length == 1 ) {
+ mockHandlers[i] = null;
+ } else {
+ mockHandlers = [];
+ }
+ };
+ $.mockjax.handler = function(i) {
+ if ( arguments.length == 1 ) {
+ return mockHandlers[i];
+ }
+ };
+})(jQuery);
\ No newline at end of file
diff --git a/spec/autocompleteBehavior.js b/spec/autocompleteBehavior.js
new file mode 100644
index 0000000..09c1a2c
--- /dev/null
+++ b/spec/autocompleteBehavior.js
@@ -0,0 +1,60 @@
+/*jslint vars: true*/
+/*global describe, it, expect, $*/
+
+describe('Autocomplete', function () {
+ 'use strict';
+
+ it('Should initialize autocomplete options', function () {
+ var input = document.createElement('input'),
+ options = { serviceUrl: '/autocomplete/service/url' },
+ autocomplete = new $.Autocomplete(input, options);
+
+ expect(autocomplete.options.serviceUrl).toEqual(options.serviceUrl);
+ expect(autocomplete.suggestionsContainer).not.toBeNull();
+ });
+
+ it('Should set autocomplete attribute to "off"', function () {
+ var input = document.createElement('input'),
+ autocomplete = new $.Autocomplete(input, {});
+
+ expect(autocomplete).not.toBeNull();
+ expect(input.getAttribute('autocomplete')).toEqual('off');
+ });
+
+ it('Should get current value', function () {
+ var input = document.createElement('input'),
+ autocomplete = new $.Autocomplete(input, {
+ lookup: [{ value: 'Jamaica', data: 'B' }]
+ });
+
+ input.value = 'Jam';
+ autocomplete.onValueChange();
+
+ expect(autocomplete.visible).toBe(true);
+ expect(autocomplete.currentValue).toEqual('Jam');
+ });
+
+ it('Verify onSelect callback', function () {
+ var input = document.createElement('input'),
+ context,
+ value,
+ data,
+ autocomplete = new $.Autocomplete(input, {
+ lookup: [{ value: 'A', data: 'B' }],
+ onSelect: function (suggestion) {
+ context = this;
+ value = suggestion.value;
+ data = suggestion.data;
+ }
+ });
+
+ input.value = 'A';
+ autocomplete.onValueChange();
+ autocomplete.select(0);
+
+ expect(context).toEqual(input);
+ expect(value).toEqual('A');
+ expect(data).toEqual('B');
+ });
+
+});
\ No newline at end of file
diff --git a/spec/lib/jasmine-1.3.1/MIT.LICENSE b/spec/lib/jasmine-1.3.1/MIT.LICENSE
new file mode 100644
index 0000000..7c435ba
--- /dev/null
+++ b/spec/lib/jasmine-1.3.1/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/spec/lib/jasmine-1.3.1/jasmine-html.js b/spec/lib/jasmine-1.3.1/jasmine-html.js
new file mode 100644
index 0000000..543d569
--- /dev/null
+++ b/spec/lib/jasmine-1.3.1/jasmine-html.js
@@ -0,0 +1,681 @@
+jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+ var results = child.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+
+ return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+ var parentDiv = this.dom.summary;
+ var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+ var parent = child[parentSuite];
+
+ if (parent) {
+ if (typeof this.views.suites[parent.id] == 'undefined') {
+ this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+ }
+ parentDiv = this.views.suites[parent.id].element;
+ }
+
+ parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+ for(var fn in jasmine.HtmlReporterHelpers) {
+ ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+ }
+};
+
+jasmine.HtmlReporter = function(_doc) {
+ var self = this;
+ var doc = _doc || window.document;
+
+ var reporterView;
+
+ var dom = {};
+
+ // Jasmine Reporter Public Interface
+ self.logRunningSpecs = false;
+
+ self.reportRunnerStarting = function(runner) {
+ var specs = runner.specs() || [];
+
+ if (specs.length == 0) {
+ return;
+ }
+
+ createReporterDom(runner.env.versionString());
+ doc.body.appendChild(dom.reporter);
+ setExceptionHandling();
+
+ reporterView = new jasmine.HtmlReporter.ReporterView(dom);
+ reporterView.addSpecs(specs, self.specFilter);
+ };
+
+ self.reportRunnerResults = function(runner) {
+ reporterView && reporterView.complete();
+ };
+
+ self.reportSuiteResults = function(suite) {
+ reporterView.suiteComplete(suite);
+ };
+
+ self.reportSpecStarting = function(spec) {
+ if (self.logRunningSpecs) {
+ self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+ };
+
+ self.reportSpecResults = function(spec) {
+ reporterView.specComplete(spec);
+ };
+
+ self.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+ };
+
+ self.specFilter = function(spec) {
+ if (!focusedSpecName()) {
+ return true;
+ }
+
+ return spec.getFullName().indexOf(focusedSpecName()) === 0;
+ };
+
+ return self;
+
+ function focusedSpecName() {
+ var specName;
+
+ (function memoizeFocusedSpec() {
+ if (specName) {
+ return;
+ }
+
+ var paramMap = [];
+ var params = jasmine.HtmlReporter.parameters(doc);
+
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ specName = paramMap.spec;
+ })();
+
+ return specName;
+ }
+
+ function createReporterDom(version) {
+ dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
+ dom.banner = self.createDom('div', { className: 'banner' },
+ self.createDom('span', { className: 'title' }, "Jasmine "),
+ self.createDom('span', { className: 'version' }, version)),
+
+ dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
+ dom.alert = self.createDom('div', {className: 'alert'},
+ self.createDom('span', { className: 'exceptions' },
+ self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
+ self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
+ dom.results = self.createDom('div', {className: 'results'},
+ dom.summary = self.createDom('div', { className: 'summary' }),
+ dom.details = self.createDom('div', { id: 'details' }))
+ );
+ }
+
+ function noTryCatch() {
+ return window.location.search.match(/catch=false/);
+ }
+
+ function searchWithCatch() {
+ var params = jasmine.HtmlReporter.parameters(window.document);
+ var removed = false;
+ var i = 0;
+
+ while (!removed && i < params.length) {
+ if (params[i].match(/catch=/)) {
+ params.splice(i, 1);
+ removed = true;
+ }
+ i++;
+ }
+ if (jasmine.CATCH_EXCEPTIONS) {
+ params.push("catch=false");
+ }
+
+ return params.join("&");
+ }
+
+ function setExceptionHandling() {
+ var chxCatch = document.getElementById('no_try_catch');
+
+ if (noTryCatch()) {
+ chxCatch.setAttribute('checked', true);
+ jasmine.CATCH_EXCEPTIONS = false;
+ }
+ chxCatch.onclick = function() {
+ window.location.search = searchWithCatch();
+ };
+ }
+};
+jasmine.HtmlReporter.parameters = function(doc) {
+ var paramStr = doc.location.search.substring(1);
+ var params = [];
+
+ if (paramStr.length > 0) {
+ params = paramStr.split('&');
+ }
+ return params;
+}
+jasmine.HtmlReporter.sectionLink = function(sectionName) {
+ var link = '?';
+ var params = [];
+
+ if (sectionName) {
+ params.push('spec=' + encodeURIComponent(sectionName));
+ }
+ if (!jasmine.CATCH_EXCEPTIONS) {
+ params.push("catch=false");
+ }
+ if (params.length > 0) {
+ link += params.join("&");
+ }
+
+ return link;
+};
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
+jasmine.HtmlReporter.ReporterView = function(dom) {
+ this.startedAt = new Date();
+ this.runningSpecCount = 0;
+ this.completeSpecCount = 0;
+ this.passedCount = 0;
+ this.failedCount = 0;
+ this.skippedCount = 0;
+
+ this.createResultsMenu = function() {
+ this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
+ this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
+ ' | ',
+ this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
+
+ this.summaryMenuItem.onclick = function() {
+ dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
+ };
+
+ this.detailsMenuItem.onclick = function() {
+ showDetails();
+ };
+ };
+
+ this.addSpecs = function(specs, specFilter) {
+ this.totalSpecCount = specs.length;
+
+ this.views = {
+ specs: {},
+ suites: {}
+ };
+
+ for (var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
+ if (specFilter(spec)) {
+ this.runningSpecCount++;
+ }
+ }
+ };
+
+ this.specComplete = function(spec) {
+ this.completeSpecCount++;
+
+ if (isUndefined(this.views.specs[spec.id])) {
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
+ }
+
+ var specView = this.views.specs[spec.id];
+
+ switch (specView.status()) {
+ case 'passed':
+ this.passedCount++;
+ break;
+
+ case 'failed':
+ this.failedCount++;
+ break;
+
+ case 'skipped':
+ this.skippedCount++;
+ break;
+ }
+
+ specView.refresh();
+ this.refresh();
+ };
+
+ this.suiteComplete = function(suite) {
+ var suiteView = this.views.suites[suite.id];
+ if (isUndefined(suiteView)) {
+ return;
+ }
+ suiteView.refresh();
+ };
+
+ this.refresh = function() {
+
+ if (isUndefined(this.resultsMenu)) {
+ this.createResultsMenu();
+ }
+
+ // currently running UI
+ if (isUndefined(this.runningAlert)) {
+ this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
+ dom.alert.appendChild(this.runningAlert);
+ }
+ this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
+
+ // skipped specs UI
+ if (isUndefined(this.skippedAlert)) {
+ this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
+ }
+
+ this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.skippedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.skippedAlert);
+ }
+
+ // passing specs UI
+ if (isUndefined(this.passedAlert)) {
+ this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
+ }
+ this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
+
+ // failing specs UI
+ if (isUndefined(this.failedAlert)) {
+ this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
+ }
+ this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
+
+ if (this.failedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.failedAlert);
+ dom.alert.appendChild(this.resultsMenu);
+ }
+
+ // summary info
+ this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
+ this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
+ };
+
+ this.complete = function() {
+ dom.alert.removeChild(this.runningAlert);
+
+ this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.failedCount === 0) {
+ dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
+ } else {
+ showDetails();
+ }
+
+ dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
+ };
+
+ return this;
+
+ function showDetails() {
+ if (dom.reporter.className.search(/showDetails/) === -1) {
+ dom.reporter.className += " showDetails";
+ }
+ }
+
+ function isUndefined(obj) {
+ return typeof obj === 'undefined';
+ }
+
+ function isDefined(obj) {
+ return !isUndefined(obj);
+ }
+
+ function specPluralizedFor(count) {
+ var str = count + " spec";
+ if (count > 1) {
+ str += "s"
+ }
+ return str;
+ }
+
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
+
+
+jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
+ this.spec = spec;
+ this.dom = dom;
+ this.views = views;
+
+ this.symbol = this.createDom('li', { className: 'pending' });
+ this.dom.symbolSummary.appendChild(this.symbol);
+
+ this.summary = this.createDom('div', { className: 'specSummary' },
+ this.createDom('a', {
+ className: 'description',
+ href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.description)
+ );
+
+ this.detail = this.createDom('div', { className: 'specDetail' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.getFullName())
+ );
+};
+
+jasmine.HtmlReporter.SpecView.prototype.status = function() {
+ return this.getSpecStatus(this.spec);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
+ this.symbol.className = this.status();
+
+ switch (this.status()) {
+ case 'skipped':
+ break;
+
+ case 'passed':
+ this.appendSummaryToSuiteDiv();
+ break;
+
+ case 'failed':
+ this.appendSummaryToSuiteDiv();
+ this.appendFailureDetail();
+ break;
+ }
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
+ this.summary.className += ' ' + this.status();
+ this.appendToSummary(this.spec, this.summary);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
+ this.detail.className += ' ' + this.status();
+
+ var resultItems = this.spec.results().getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ this.detail.appendChild(messagesDiv);
+ this.dom.details.appendChild(this.detail);
+ }
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
+ this.suite = suite;
+ this.dom = dom;
+ this.views = views;
+
+ this.element = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
+ );
+
+ this.appendToSummary(this.suite, this.element);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.status = function() {
+ return this.getSpecStatus(this.suite);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
+ this.element.className += " " + this.status();
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
+
+/* @deprecated Use jasmine.HtmlReporter instead
+ */
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/spec/lib/jasmine-1.3.1/jasmine.css b/spec/lib/jasmine-1.3.1/jasmine.css
new file mode 100644
index 0000000..8c008dc
--- /dev/null
+++ b/spec/lib/jasmine-1.3.1/jasmine.css
@@ -0,0 +1,82 @@
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+#HTMLReporter a { text-decoration: none; }
+#HTMLReporter a:hover { text-decoration: underline; }
+#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
+#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
+#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
+#HTMLReporter .version { color: #aaaaaa; }
+#HTMLReporter .banner { margin-top: 14px; }
+#HTMLReporter .duration { color: #aaaaaa; float: right; }
+#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
+#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
+#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
+#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
+#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
+#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
+#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
+#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
+#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
+#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+#HTMLReporter .runningAlert { background-color: #666666; }
+#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
+#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
+#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
+#HTMLReporter .passingAlert { background-color: #a6b779; }
+#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
+#HTMLReporter .failingAlert { background-color: #cf867e; }
+#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
+#HTMLReporter .results { margin-top: 14px; }
+#HTMLReporter #details { display: none; }
+#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
+#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter.showDetails .summary { display: none; }
+#HTMLReporter.showDetails #details { display: block; }
+#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter .summary { margin-top: 14px; }
+#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
+#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
+#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
+#HTMLReporter .description + .suite { margin-top: 0; }
+#HTMLReporter .suite { margin-top: 14px; }
+#HTMLReporter .suite a { color: #333333; }
+#HTMLReporter #details .specDetail { margin-bottom: 28px; }
+#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
+#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
+#HTMLReporter .resultMessage span.result { display: block; }
+#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
+
+#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
+#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
+#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
+#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
+#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
+#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
+#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
+#TrivialReporter .runner.running { background-color: yellow; }
+#TrivialReporter .options { text-align: right; font-size: .8em; }
+#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
+#TrivialReporter .suite .suite { margin: 5px; }
+#TrivialReporter .suite.passed { background-color: #dfd; }
+#TrivialReporter .suite.failed { background-color: #fdd; }
+#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
+#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
+#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
+#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
+#TrivialReporter .spec.skipped { background-color: #bbb; }
+#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
+#TrivialReporter .passed { background-color: #cfc; display: none; }
+#TrivialReporter .failed { background-color: #fbb; }
+#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
+#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
+#TrivialReporter .resultMessage .mismatch { color: black; }
+#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
+#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
+#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
+#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
+#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
diff --git a/spec/lib/jasmine-1.3.1/jasmine.js b/spec/lib/jasmine-1.3.1/jasmine.js
new file mode 100644
index 0000000..6b3459b
--- /dev/null
+++ b/spec/lib/jasmine-1.3.1/jasmine.js
@@ -0,0 +1,2600 @@
+var isCommonJS = typeof window == "undefined" && typeof exports == "object";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use jasmine.undefined
instead of undefined
, since undefined
is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Maximum levels of nesting that will be included when an object is pretty-printed
+ */
+jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+/**
+ * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
+ * Set to false to let the exception bubble up in the browser.
+ *
+ */
+jasmine.CATCH_EXCEPTIONS = true;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
+ * attributes on the object.
+ *
+ * @example
+ * // don't care about any other attributes than foo.
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
+ *
+ * @param sample {Object} sample
+ * @returns matchable object for the sample
+ */
+jasmine.objectContaining = function (sample) {
+ return new jasmine.Matchers.ObjectContaining(sample);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a disabled Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ * @return {jasmine.Matchers}
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.source != b.source)
+ mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
+
+ if (a.ignoreCase != b.ignoreCase)
+ mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.global != b.global)
+ mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.multiline != b.multiline)
+ mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.sticky != b.sticky)
+ mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
+
+ return (mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a.jasmineMatches) {
+ return a.jasmineMatches(b);
+ }
+
+ if (b.jasmineMatches) {
+ return b.jasmineMatches(a);
+ }
+
+ if (a instanceof jasmine.Matchers.ObjectContaining) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.ObjectContaining) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (a instanceof RegExp && b instanceof RegExp) {
+ return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ if (!jasmine.CATCH_EXCEPTIONS) {
+ this.func.apply(this.spec);
+ }
+ else {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that compares the actual to NaN.
+ */
+jasmine.Matchers.prototype.toBeNaN = function() {
+ this.message = function() {
+ return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
+ };
+
+ return (this.actual !== this.actual);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
+ var positiveMessage = "";
+ if (this.actual.callCount === 0) {
+ positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
+ } else {
+ positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
+ }
+ return [positiveMessage, invertedMessage];
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision, as number of decimal places
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} [expected]
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineToString = function() {
+ return '';
+};
+
+jasmine.Matchers.ObjectContaining = function (sample) {
+ this.sample = sample;
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ var env = jasmine.getEnv();
+
+ var hasKey = function(obj, keyName) {
+ return obj != null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in this.sample) {
+ if (!hasKey(other, property) && hasKey(this.sample, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
+ }
+ }
+
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+ return "";
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if everything below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (!obj.hasOwnProperty(property)) continue;
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Array");
+ return;
+ }
+
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Object");
+ return;
+ }
+
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+
+ // parallel to blocks. each true value in this array means the block will
+ // get executed even if we abort
+ this.ensured = [];
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.blocks.unshift(block);
+ this.ensured.unshift(ensure);
+};
+
+jasmine.Queue.prototype.add = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.blocks.push(block);
+ this.ensured.push(ensure);
+};
+
+jasmine.Queue.prototype.insertNext = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.ensured.splice((this.index + this.offset + 1), 0, ensure);
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this), true);
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true);
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true);
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true);
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 3,
+ "build": 1,
+ "revision": 1354556913
+};
diff --git a/spec/runner.html b/spec/runner.html
new file mode 100644
index 0000000..108ba3d
--- /dev/null
+++ b/spec/runner.html
@@ -0,0 +1,32 @@
+
+
+
+ Autocomplete Spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/jquery.autocomplete.js b/src/jquery.autocomplete.js
index baf700a..1fe5488 100644
--- a/src/jquery.autocomplete.js
+++ b/src/jquery.autocomplete.js
@@ -1,71 +1,104 @@
/**
-* Ajax Autocomplete for jQuery, version 1.1.5
-* (c) 2012 Tomas Kirda, Vytautas Pranskunas
+* Ajax Autocomplete for jQuery, version 1.2
+* (c) 2012 Tomas Kirda
*
* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/
*
-* Last Review: 11/08/2012
+* Last Review: 12/18/2012
*/
-/*jslint browser: true, white: true, plusplus: true */
+/*jslint browser: true, white: true, plusplus: true, vars: true */
/*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */
(function ($) {
'use strict';
- var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g');
+ var utils = (function () {
+ return {
- function fnFormatResult(value, data, currentValue) {
- var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
- return value.replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
- }
+ extend: function (target, source) {
+ return $.extend(target, source);
+ },
+
+ addEvent: function (element, eventType, handler) {
+ if (element.addEventListener) {
+ element.addEventListener(eventType, handler, false);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + eventType, handler);
+ } else {
+ throw new Error('Browser doesn\'t support addEventListener or attachEvent');
+ }
+ },
+
+ removeEvent: function (element, eventType, handler) {
+ if (element.removeEventListener) {
+ element.removeEventListener(eventType, handler, false);
+ } else if (element.detachEvent) {
+ element.detachEvent('on' + eventType, handler);
+ }
+ },
+
+ createNode: function (html) {
+ var div = document.createElement('div');
+ div.innerHTML = html;
+ return div.firstChild;
+ }
+
+ };
+ }());
function Autocomplete(el, options) {
- this.el = $(el);
- this.el.attr('autocomplete', 'off');
- this.suggestions = [];
- this.data = [];
- this.badQueries = [];
- this.selectedIndex = -1;
- this.currentValue = this.el.val();
- this.intervalId = 0;
- this.cachedResponse = [];
- this.onChangeInterval = null;
- this.onChange = null;
- this.ignoreValueChange = false;
- this.serviceUrl = options.serviceUrl;
- this.isLocal = false;
- this.options = {
- autoSubmit: false,
- minChars: 1,
- maxHeight: 300,
- deferRequestBy: 0,
- width: 0,
- highlight: true,
- params: {},
- fnFormatResult: fnFormatResult,
- delimiter: null,
- zIndex: 9999
+ var that = this,
+ defaults = {
+ minChars: 1,
+ maxHeight: 300,
+ deferRequestBy: 0,
+ width: 0,
+ highlight: true,
+ params: {},
+ formatResult: Autocomplete.formatResult,
+ delimiter: null,
+ zIndex: 9999,
+ type: 'GET',
+ noCache: false,
+ enforce: false
+ };
+
+ // Shared variables:
+ that.element = el;
+ that.el = $(el);
+ that.suggestions = [];
+ that.badQueries = [];
+ that.selectedIndex = -1;
+ that.currentValue = that.element.value;
+ that.intervalId = 0;
+ that.cachedResponse = [];
+ that.onChangeInterval = null;
+ that.onChange = null;
+ that.ignoreValueChange = false;
+ that.isLocal = false;
+ that.suggestionsContainer = null;
+ that.options = defaults;
+ that.classes = {
+ selected: 'autocomplete-selected',
+ suggestion: 'autocomplete-suggestion'
};
- this.initialize();
- this.setOptions(options);
- this.el.data('autocomplete', this);
+
+ // Initialize and set options:
+ that.initialize();
+ that.setOptions(options);
}
- $.fn.autocomplete = function (options, args) {
- var instance;
+ Autocomplete.utils = utils;
- if (typeof options === 'string') {
- instance = this.data('autocomplete');
- if (typeof instance[options] === 'function') {
- instance[options](args);
- }
- } else {
- instance = new Autocomplete(this.get(0) || $(''), options);
- }
+ $.Autocomplete = Autocomplete;
- return instance;
+ Autocomplete.formatResult = function (suggestion, currentValue) {
+ var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'),
+ pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
+
+ return suggestion.value.replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
};
Autocomplete.prototype = {
@@ -73,60 +106,78 @@
killerFn: null,
initialize: function () {
- var me = this,
- uid = Math.floor(Math.random() * 0x100000).toString(16),
- autocompleteElId = 'Autocomplete_' + uid,
- onKeyPress = function (e) {
- me.onKeyPress(e);
- };
+ var that = this,
+ suggestionSelector = '.' + that.classes.suggestion;
+
+ // Remove autocomplete attribute to prevent native suggestions:
+ this.element.setAttribute('autocomplete', 'off');
this.killerFn = function (e) {
- if ($(e.target).parents('.autocomplete').size() === 0) {
- me.killSuggestions();
- me.disableKillerFn();
+ if ($(e.target).closest('.autocomplete').length === 0) {
+ that.killSuggestions();
+ that.disableKillerFn();
}
};
- if (!this.options.width) {
+ // Determine suggestions width:
+ if (!this.options.width || this.options.width === 'auto') {
this.options.width = this.el.outerWidth();
}
- this.mainContainerId = 'AutocompleteContainter_' + uid;
+ this.suggestionsContainer = Autocomplete.utils.createNode('');
- $('').appendTo('body');
+ var container = $(this.suggestionsContainer);
+
+ container.appendTo('body').width(this.options.width);
+
+ // Listen for mouse over event on suggestions list:
+ container.on('mouseover', suggestionSelector, function () {
+ that.activate($(this).data('index'));
+ });
+
+ // Listen for click event on suggestions list:
+ container.on('click', suggestionSelector, function () {
+ that.select($(this).data('index'));
+ });
- this.container = $('#' + autocompleteElId);
this.fixPosition();
+ // Opera does not like keydown:
if (window.opera) {
- this.el.keypress(onKeyPress);
+ this.el.on('keypress', function (e) { that.onKeyPress(e); });
} else {
- this.el.keydown(onKeyPress);
+ this.el.on('keydown', function (e) { that.onKeyPress(e); });
}
- this.el.keyup(function (e) { me.onKeyUp(e); });
- this.el.blur(function () { me.enableKillerFn(); });
- this.el.focus(function () { me.fixPosition(); });
- this.el.change(function () { me.onValueChanged(); });
+ this.el.on('keyup', function (e) { that.onKeyUp(e); });
+ this.el.on('blur', function () { that.onBlur(); });
+ this.el.on('focus', function () { that.fixPosition(); });
},
- extendOptions: function (options) {
- $.extend(this.options, options);
+ onBlur: function () {
+ this.enableKillerFn();
},
- setOptions: function (options) {
- var o = this.options;
+ setOptions: function (suppliedOptions) {
+ var options = this.options;
- this.extendOptions(options);
+ utils.extend(options, suppliedOptions);
- if (o.lookup || o.isLocal) {
- this.isLocal = true;
- if ($.isArray(o.lookup)) { o.lookup = { suggestions: o.lookup, data: [] }; }
+ this.isLocal = $.isArray(options.lookup);
+
+ // Transform lookup array if it's string array:
+ if (this.isLocal && typeof options.lookup[0] === 'string') {
+ options.lookup = $.map(options.lookup, function (value) {
+ return { value: value, data: null };
+ });
}
- $('#' + this.mainContainerId).css({ zIndex: o.zIndex });
-
- this.container.css({ maxHeight: o.maxHeight + 'px', width: o.width });
+ // Adjust height, width and z-index:
+ $(this.suggestionsContainer).css({
+ 'max-height': options.maxHeight + 'px',
+ 'width': options.width,
+ 'z-index': options.zIndex
+ });
},
clearCache: function () {
@@ -144,25 +195,28 @@
fixPosition: function () {
var offset = this.el.offset();
- $('#' + this.mainContainerId).css({ top: (offset.top + this.el.outerHeight()) + 'px', left: offset.left + 'px' });
+ $(this.suggestionsContainer).css({
+ top: (offset.top + this.el.outerHeight()) + 'px',
+ left: offset.left + 'px'
+ });
},
enableKillerFn: function () {
- var me = this;
- $(document).bind('click', me.killerFn);
+ var that = this;
+ $(document).on('click', that.killerFn);
},
disableKillerFn: function () {
- var me = this;
- $(document).unbind('click', me.killerFn);
+ var that = this;
+ $(document).off('click', that.killerFn);
},
killSuggestions: function () {
- var me = this;
- me.stopKillSuggestions();
- me.intervalId = window.setInterval(function () {
- me.hide();
- me.stopKillSuggestions();
+ var that = this;
+ that.stopKillSuggestions();
+ that.intervalId = window.setInterval(function () {
+ that.hide();
+ that.stopKillSuggestions();
}, 300);
},
@@ -170,12 +224,14 @@
window.clearInterval(this.intervalId);
},
- onValueChanged: function () {
- this.change(this.selectedIndex);
- },
-
onKeyPress: function (e) {
- if (this.disabled || !this.enabled) {
+ // If suggestions are hidden and user presses arrow down, display suggestions:
+ if (!this.disabled && !this.visible && e.keyCode === 40 && this.currentValue) {
+ this.suggest();
+ return;
+ }
+
+ if (this.disabled || !this.visible) {
return;
}
@@ -227,7 +283,9 @@
if (this.options.deferRequestBy > 0) {
// Defer lookup in case when value changes very quickly:
var me = this;
- this.onChangeInterval = setInterval(function () { me.onValueChange(); }, this.options.deferRequestBy);
+ this.onChangeInterval = setInterval(function () {
+ me.onValueChange();
+ }, this.options.deferRequestBy);
} else {
this.onValueChange();
}
@@ -236,7 +294,7 @@
onValueChange: function () {
clearInterval(this.onChangeInterval);
- this.currentValue = this.el.val();
+ this.currentValue = this.element.value;
var q = this.getQuery(this.currentValue);
this.selectedIndex = -1;
@@ -252,58 +310,56 @@
}
},
- getQuery: function (val) {
- var d, arr;
- d = this.options.delimiter;
- if (!d) {
- return $.trim(val);
+ getQuery: function (value) {
+ var delimiter = this.options.delimiter,
+ parts;
+
+ if (!delimiter) {
+ return $.trim(value);
}
- arr = val.split(d);
- return $.trim(arr[arr.length - 1]);
+ parts = value.split(delimiter);
+ return $.trim(parts[parts.length - 1]);
},
getSuggestionsLocal: function (q) {
- var ret, arr, len, val, i;
-
- arr = this.options.lookup;
- len = arr.suggestions.length;
- ret = { suggestions: [], data: [] };
q = q.toLowerCase();
- for (i = 0; i < len; i++) {
- val = arr.suggestions[i];
- if (val.toLowerCase().indexOf(q) === 0) {
- ret.suggestions.push(val);
- ret.data.push(arr.data[i]);
- }
- }
-
- return ret;
+ return {
+ suggestions: $.grep(this.options.lookup, function (suggestion) {
+ return suggestion.value.toLowerCase().indexOf(q) !== -1;
+ })
+ };
},
getSuggestions: function (q) {
- var cr, me;
+ var response,
+ that = this,
+ options = that.options;
- cr = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q];
+ response = that.isLocal ? that.getSuggestionsLocal(q) : that.cachedResponse[q];
- if (cr && $.isArray(cr.suggestions)) {
- this.suggestions = cr.suggestions;
- this.data = cr.data;
- this.suggest();
- } else if (!this.isBadQuery(q)) {
- me = this;
- me.options.params.query = q;
- $.get(this.serviceUrl, me.options.params, function (txt) {
- me.processResponse(txt);
- }, 'text');
+ if (response && $.isArray(response.suggestions)) {
+ that.suggestions = response.suggestions;
+ that.suggest();
+ } else if (!that.isBadQuery(q)) {
+ that.options.params.query = q;
+ $.ajax({
+ url: options.serviceUrl,
+ data: options.params,
+ type: options.type,
+ dataType: 'text'
+ }).done(function (txt) {
+ that.processResponse(txt);
+ });
}
},
isBadQuery: function (q) {
- var i = this.badQueries.length;
+ var badQueries = this.badQueries,
+ i = badQueries.length;
while (i--) {
- if (q.indexOf(this.badQueries[i]) === 0) {
+ if (q.indexOf(badQueries[i]) === 0) {
return true;
}
}
@@ -312,9 +368,9 @@
},
hide: function () {
- this.enabled = false;
+ this.visible = false;
this.selectedIndex = -1;
- this.container.hide();
+ $(this.suggestionsContainer).hide();
},
suggest: function () {
@@ -323,53 +379,41 @@
return;
}
- var me, len, div, f, v, i, s, mOver, mClick;
-
- me = this;
- len = this.suggestions.length;
- f = this.options.fnFormatResult;
- v = this.getQuery(this.currentValue);
-
- mOver = function (xi) {
- return function () {
- me.activate(xi);
- };
- };
-
- mClick = function (xi) {
- return function () {
- me.select(xi);
- };
- };
-
- this.container.hide().empty();
+ var len = this.suggestions.length,
+ formatResults = this.options.formatResult,
+ value = this.getQuery(this.currentValue),
+ suggestion,
+ className = this.classes.suggestion,
+ classSelected = this.classes.selected,
+ container = $(this.suggestionsContainer),
+ html = '',
+ i;
+ // Build suggestions inner HTML:
for (i = 0; i < len; i++) {
- s = this.suggestions[i];
- div = $((me.selectedIndex === i ? '' + f(s, this.data[i], v) + '
');
- div.mouseover(mOver(i));
- div.click(mClick(i));
- this.container.append(div);
+ suggestion = this.suggestions[i];
+ html += '' + formatResults(suggestion, value) + '
';
}
- this.enabled = true;
- this.container.show();
+ container.html(html).show();
+ this.visible = true;
+
+ // Select first value by default:
+ this.selectedIndex = 0;
+ container.children().first().addClass(classSelected);
},
processResponse: function (text) {
- /*jslint evil: true */
- var response;
+ var response = $.parseJSON(text);
- try {
- response = eval('(' + text + ')');
- } catch (err) {
- return;
- }
-
- if (!$.isArray(response.data)) {
- response.data = [];
+ // If suggestions is string array, convert them to supported format:
+ if (typeof response.suggestions[0] === 'string') {
+ response.suggestions = $.map(response.suggestions, function (value) {
+ return { value: value, data: null };
+ });
}
+ // Cache results if cache is not disabled:
if (!this.options.noCache) {
this.cachedResponse[response.query] = response;
if (response.suggestions.length === 0) {
@@ -377,53 +421,37 @@
}
}
+ // Display suggestions only if returned query matches current value:
if (response.query === this.getQuery(this.currentValue)) {
this.suggestions = response.suggestions;
- this.data = response.data;
this.suggest();
}
},
activate: function (index) {
- var divs, activeItem;
+ var activeItem,
+ selected = this.classes.selected,
+ container = $(this.suggestionsContainer),
+ children = container.children();
- divs = this.container.children();
- // Clear previous selection:
- if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
- $(divs.get(this.selectedIndex)).removeClass();
- }
+ container.children('.' + selected).removeClass(selected);
this.selectedIndex = index;
- if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
- activeItem = divs.get(this.selectedIndex);
- $(activeItem).addClass('selected');
+ if (this.selectedIndex !== -1 && children.length > this.selectedIndex) {
+ activeItem = children.get(this.selectedIndex);
+ $(activeItem).addClass(selected);
return activeItem;
}
return null;
},
- deactivate: function (div, index) {
- div.className = '';
- if (this.selectedIndex === index) {
- this.selectedIndex = -1;
- }
- },
-
select: function (i) {
- var selectedValue, f;
-
- selectedValue = this.suggestions[i];
+ var selectedValue = this.suggestions[i];
if (selectedValue) {
this.el.val(selectedValue);
- if (this.options.autoSubmit) {
- f = this.el.parents('form');
- if (f.length > 0) {
- f.get(0).submit();
- }
- }
this.ignoreValueChange = true;
this.hide();
this.onSelect(i);
@@ -431,23 +459,19 @@
},
change: function (i) {
- var selectedValue, onChange, me, s, d;
-
- me = this;
- selectedValue = this.suggestions[i];
+ var onChange,
+ me = this,
+ selectedValue = this.suggestions[i],
+ suggestion;
if (selectedValue) {
- s = me.suggestions[i];
- d = me.data[i];
- me.el.val(me.getValue(s));
- } else {
- s = '';
- d = -1;
- }
+ suggestion = me.suggestions[i];
+ me.el.val(me.getValue(suggestion.value));
- onChange = me.options.onChange;
- if ($.isFunction(onChange)) {
- onChange(s, d, me.el);
+ onChange = me.options.onChange;
+ if ($.isFunction(onChange)) {
+ onChange(suggestion, me.el);
+ }
}
},
@@ -457,7 +481,7 @@
}
if (this.selectedIndex === 0) {
- this.container.children().get(0).className = '';
+ $(this.suggestionsContainer).children().first().removeClass(this.classes.selected);
this.selectedIndex = -1;
this.el.val(this.currentValue);
return;
@@ -474,54 +498,80 @@
this.adjustScroll(this.selectedIndex + 1);
},
- adjustScroll: function (i) {
- var activeItem, offsetTop, upperBound, lowerBound;
+ adjustScroll: function (index) {
+ var activeItem = this.activate(index),
+ offsetTop,
+ upperBound,
+ lowerBound,
+ heightDelta = 25;
- activeItem = this.activate(i);
+ if (!activeItem) {
+ return;
+ }
+
offsetTop = activeItem.offsetTop;
- upperBound = this.container.scrollTop();
- lowerBound = upperBound + this.options.maxHeight - 25;
+ upperBound = $(this.suggestionsContainer).scrollTop();
+ lowerBound = upperBound + this.options.maxHeight - heightDelta;
if (offsetTop < upperBound) {
- this.container.scrollTop(offsetTop);
+ $(this.suggestionsContainer).scrollTop(offsetTop);
} else if (offsetTop > lowerBound) {
- this.container.scrollTop(offsetTop - this.options.maxHeight + 25);
+ $(this.suggestionsContainer).scrollTop(offsetTop - this.options.maxHeight + heightDelta);
}
- this.el.val(this.getValue(this.suggestions[i]));
+ this.el.val(this.getValue(this.suggestions[index].value));
},
- onSelect: function (i) {
- var me = this,
- callback = me.options.onSelect,
- sugestion = me.suggestions[i],
- data = me.data[i];
+ onSelect: function (index) {
+ var that = this,
+ onSelectCallback = that.options.onSelect,
+ suggestion = that.suggestions[index];
- me.el.val(me.getValue(sugestion));
+ that.el.val(that.getValue(suggestion.value));
- if ($.isFunction(callback)) {
- callback(sugestion, data, me.el);
+ if ($.isFunction(onSelectCallback)) {
+ onSelectCallback.call(that.element, suggestion);
}
},
getValue: function (value) {
- var me = this,
- separator = me.options.delimiter,
+ var that = this,
+ delimiter = that.options.delimiter,
currentValue,
- array;
+ parts;
- if (!separator) {
+ if (!delimiter) {
return value;
}
- currentValue = me.currentValue;
- array = currentValue.split(separator);
+ currentValue = that.currentValue;
+ parts = currentValue.split(delimiter);
- if (array.length === 1) {
+ if (parts.length === 1) {
return value;
}
- return currentValue.substr(0, currentValue.length - array[array.length - 1].length) + value;
+ return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
}
};
+
+ // Create chainable jQuery plugin:
+ $.fn.autocomplete = function (options, args) {
+ return this.each(function () {
+ var dataKey = 'autocomplete',
+ inputElement = $(this),
+ instance;
+
+ if (typeof options === 'string') {
+ instance = inputElement.data(dataKey);
+ if (typeof instance[options] === 'function') {
+ instance[options](args);
+ }
+ } else {
+ instance = new Autocomplete(this, options);
+ inputElement.data(dataKey, instance);
+ }
+ });
+ };
+
}(jQuery));