From 0042fad94c8656248dfa9c9765330cfede8a085e Mon Sep 17 00:00:00 2001 From: John Resig Date: Mon, 22 Feb 2010 18:06:13 -0500 Subject: [PATCH 01/59] Update jQuery to version 1.4.2. --- jquery-1.2.6.js | 3550 ------------------------ jquery-1.3.2.min.js | 19 - jquery-1.4.2.js | 6240 +++++++++++++++++++++++++++++++++++++++++++ test-static-01.html | 2 +- test-static-02.html | 2 +- test-static-03.html | 2 +- test-static-04.html | 2 +- test-static-05.html | 2 +- test-static-06.html | 2 +- test-static-07.html | 2 +- 10 files changed, 6247 insertions(+), 3576 deletions(-) delete mode 100644 jquery-1.2.6.js delete mode 100644 jquery-1.3.2.min.js create mode 100644 jquery-1.4.2.js diff --git a/jquery-1.2.6.js b/jquery-1.2.6.js deleted file mode 100644 index d5a7eee..0000000 --- a/jquery-1.2.6.js +++ /dev/null @@ -1,3550 +0,0 @@ -(function(){ -/* - * jQuery 1.2.6 - New Wave Javascript - * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ - * $Rev: 5685 $ - */ - -// Map over jQuery in case of overwrite -var _jQuery = window.jQuery, -// Map over the $ in case of overwrite - _$ = window.$; - -var jQuery = window.jQuery = window.$ = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); -}; - -// A simple way to check for HTML strings or ID strings -// (both of which we optimize for) -var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/, - -// Is it a simple selector - isSimple = /^.[^:#\[\.]*$/, - -// Will speed up references to undefined, and allows munging its name. - undefined; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - return this; - } - // Handle HTML strings - if ( typeof selector == "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Make sure an element was located - if ( elem ){ - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - return jQuery( elem ); - } - selector = []; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); - - return this.setArray(jQuery.makeArray(selector)); - }, - - // The current version of jQuery being used - jquery: "1.2.6", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // The number of elements contained in the matched element set - length: 0, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == undefined ? - - // Return a 'clean' array - jQuery.makeArray( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - var ret = -1; - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( name.constructor == String ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text != "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) - // The elements to wrap the target around - jQuery( html, this[0].ownerDocument ) - .clone() - .insertBefore( this[0] ) - .map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }) - .append(this); - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, false, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, true, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - find: function( selector ) { - this.query = selector; - var elems = jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - }); - - return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? - jQuery.unique( elems ) : - elems ); - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var clone = this.cloneNode(true), - container = document.createElement("div"); - container.appendChild(clone); - return jQuery.clean([container.innerHTML])[0]; - } else - return this.cloneNode(true); - }); - - // Need to set the expando to null on the cloned set if it exists - // removeData doesn't work here, IE removes it from the original as well - // this is primarily for IE but the data expando shouldn't be copied over in any browser - var clone = ret.find("*").andSelf().each(function(){ - if ( this[ expando ] != undefined ) - this[ expando ] = null; - }); - - // Copy the events from the original to the clone - if ( events === true ) - this.find("*").andSelf().each(function(i){ - if (this.nodeType == 3) - return; - var events = jQuery.data( this, "events" ); - - for ( var type in events ) - for ( var handler in events[ type ] ) - jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); - }); - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, this ) ); - }, - - not: function( selector ) { - if ( selector.constructor == String ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ) ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector == 'string' ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, - - is: function( selector ) { - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, - - hasClass: function( selector ) { - return this.is( "." + selector ); - }, - - val: function( value ) { - if ( value == undefined ) { - - if ( this.length ) { - var elem = this[0]; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - - // Everything else, we just grab the value - } else - return (this[0].value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - if( value.constructor == Number ) - value += ''; - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value == undefined ? - (this[0] ? - this[0].innerHTML : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - data: function( key, value ){ - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - if ( data === undefined && this.length ) - data = jQuery.data( this[0], key ); - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - } else - return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ - jQuery.data( this, key, value ); - }); - }, - - removeData: function( key ){ - return this.each(function(){ - jQuery.removeData( this, key ); - }); - }, - - domManip: function( args, table, reverse, callback ) { - var clone = this.length > 1, elems; - - return this.each(function(){ - if ( !elems ) { - elems = jQuery.clean( args, this.ownerDocument ); - - if ( reverse ) - elems.reverse(); - } - - var obj = this; - - if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) - obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); - - var scripts = jQuery( [] ); - - jQuery.each(elems, function(){ - var elem = clone ? - jQuery( this ).clone( true )[0] : - this; - - // execute all scripts after the elements have been injected - if ( jQuery.nodeName( elem, "script" ) ) - scripts = scripts.add( elem ); - else { - // Remove any inner scripts for later evaluation - if ( elem.nodeType == 1 ) - scripts = scripts.add( jQuery( "script", elem ).remove() ); - - // Inject the elements into the document - callback.call( obj, elem ); - } - }); - - scripts.each( evalScript ); - }); - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( target.constructor == Boolean ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target != "object" && typeof target != "function" ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) - continue; - - // Recurse if we're merging object values - if ( deep && copy && typeof copy == "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); - - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; - - } - - // Return the modified object - return target; -}; - -var expando = "jQuery" + now(), uuid = 0, windowData = {}, - // exclude the following css properties to add px - exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning this function. - isFunction: function( fn ) { - return !!fn && typeof fn != "string" && !fn.nodeName && - fn.constructor != Array && /^[\s[]?function/.test( fn + "" ); - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - data = jQuery.trim( data ); - - if ( data ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.browser.msie ) - script.text = data; - else - script.appendChild( document.createTextNode( data ) ); - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - cache: {}, - - data: function( elem, name, data ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // Compute a unique ID for the element - if ( !id ) - id = elem[ expando ] = ++uuid; - - // Only generate the data cache if we're - // trying to access or manipulate it - if ( name && !jQuery.cache[ id ] ) - jQuery.cache[ id ] = {}; - - // Prevent overriding the named cache with undefined values - if ( data !== undefined ) - jQuery.cache[ id ][ name ] = data; - - // Return the named cache data, or the ID for the element - return name ? - jQuery.cache[ id ][ name ] : - id; - }, - - removeData: function( elem, name ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( jQuery.cache[ id ] ) { - // Remove the section of cache data - delete jQuery.cache[ id ][ name ]; - - // If we've removed all the data, remove the element's cache - name = ""; - - for ( name in jQuery.cache[ id ] ) - break; - - if ( !name ) - jQuery.removeData( elem ); - } - - // Otherwise, we want to remove all of the element's data - } else { - // Clean up the element expando - try { - delete elem[ expando ]; - } catch(e){ - // IE has trouble directly removing the expando - // but it's ok with using removeAttribute - if ( elem.removeAttribute ) - elem.removeAttribute( expando ); - } - - // Completely remove the data cache - delete jQuery.cache[ id ]; - } - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, length = object.length; - - if ( args ) { - if ( length == undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( ; i < length; ) - if ( callback.apply( object[ i++ ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( length == undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames != undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use hasClass("class") - has: function( elem, className ) { - return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - var padding = 0, border = 0; - jQuery.each( which, function() { - padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - val -= Math.round(padding + border); - } - - if ( jQuery(elem).is(":visible") ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, val); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret, style = elem.style; - - // A helper method for determining if an element's values are broken - function color( elem ) { - if ( !jQuery.browser.safari ) - return false; - - // defaultView is cached - var ret = defaultView.getComputedStyle( elem, null ); - return !ret || ret.getPropertyValue("color") == ""; - } - - // We need to handle opacity special in IE - if ( name == "opacity" && jQuery.browser.msie ) { - ret = jQuery.attr( style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - // Opera sometimes will give the wrong display answer, this fixes it, see #2037 - if ( jQuery.browser.opera && name == "display" ) { - var save = style.outline; - style.outline = "0 solid black"; - style.outline = save; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && style && style[ name ] ) - ret = style[ name ]; - - else if ( defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var computedStyle = defaultView.getComputedStyle( elem, null ); - - if ( computedStyle && !color( elem ) ) - ret = computedStyle.getPropertyValue( name ); - - // If the element isn't reporting its values properly in Safari - // then some display: none elements are involved - else { - var swap = [], stack = [], a = elem, i = 0; - - // Locate all of the parent display: none elements - for ( ; a && color(a); a = a.parentNode ) - stack.unshift(a); - - // Go through and make them visible, but in reverse - // (It would be better if we knew the exact display type that they had) - for ( ; i < stack.length; i++ ) - if ( color( stack[ i ] ) ) { - swap[ i ] = stack[ i ].style.display; - stack[ i ].style.display = "block"; - } - - // Since we flip the display style, we have to handle that - // one special, otherwise get the value - ret = name == "display" && swap[ stack.length - 1 ] != null ? - "none" : - ( computedStyle && computedStyle.getPropertyValue( name ) ) || ""; - - // Finally, revert the display styles back - for ( i = 0; i < swap.length; i++ ) - if ( swap[ i ] != null ) - stack[ i ].style.display = swap[ i ]; - } - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - } - - return ret; - }, - - clean: function( elems, context ) { - var ret = []; - context = context || document; - // !context.createElement fails in IE with an error but returns typeof 'object' - if (typeof context.createElement == 'undefined') - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - jQuery.each(elems, function(i, elem){ - if ( !elem ) - return; - - if ( elem.constructor == Number ) - elem += ''; - - // Convert html string into DOM nodes - if ( typeof elem == "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and + + + + + + diff --git a/test-static-07.html b/test-static-07.html index 76aa01f..bd46c16 100644 --- a/test-static-07.html +++ b/test-static-07.html @@ -1,6 +1,6 @@ - + From 0451de18d57d3401bd4cc021facbe5fd63b5aae6 Mon Sep 17 00:00:00 2001 From: John Resig Date: Tue, 23 Feb 2010 02:07:02 -0500 Subject: [PATCH 02/59] jQuery Hotkeys rewritten to work with jQuery 1.4.2. A bunch of old code stripped and bugs fixed. --- README.md | 80 +++-------- jquery.hotkeys.js | 335 ++++++++++++-------------------------------- test-static-02.html | 26 ++-- test-static-04.html | 2 +- 4 files changed, 120 insertions(+), 323 deletions(-) diff --git a/README.md b/README.md index 4abcd7a..22873d6 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,32 @@ #About -**jQuery.hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. +**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. -It is based on a library [Shortcut.js](http://www.openjs.com/scripts/events/keyboard_shortcuts/shortcut.js) written by [Binny V A](http://www.openjs.com/). +This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](http://github.com/tzuryby/hotkeys) The syntax is as follows: -
-$(expression).bind(,, );
-$(expression).unbind(,, );
 
-$(document).bind('keydown', 'Ctrl+a', fn);
-
-// e.g. replace '$' sign with 'EUR'
-$('input.foo').bind('keyup', '$', function(){
-    this.value = this.value.replace('$', 'EUR');
-});
-
-$('div.foo').unbind('keydown', 'Ctrl+a', fn);
-
-## [Live Demo](http://jshotkeys.googlepages.com/test-static-01.html) + $(expression).bind(types, keys, handler); + $(expression).unbind(types, handler); + + $(document).bind('keydown', 'ctrl+a', fn); + + // e.g. replace '$' sign with 'EUR' + $('input.foo').bind('keyup', '$', function(){ + this.value = this.value.replace('$', 'EUR'); + }); ## Types Supported types are `'keydown'`, `'keyup'` and `'keypress'` -## Options -The options are `'combi'` i.e. the key combination, and `'disableInInput'` which allow your code not to be executed when the cursor is located inside an input ( `$(elem).is('input') || $(elem).is('textarea')` ). - -As you can see, the key combination can be passed as string or as an object. You may pass an object in case you wish to override the default option for `disableInInput` which is set to `false`: -
-$(document).bind('keydown', {combi:'a', disableinInput: true}, fn);
-
-I.e. when cursor is within an input field, `'a'` will be inserted into the input field without interfering. +## Notes If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift -Modifiers are case insensitive, i.e. 'Ctrl+a' 'ctrl+a'. - -## Handler -In previous versions there was an option propagate which is removed now and implemented at the user code level. - -When using jQuery, if an event handler returns false, jQuery will call `stopPropagation()` and `preventDefault()` +Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing. ## jQuery Compatibility -Tested with *jQuery 1.2.6* + +Works with jQuery 1.4.2 and newer. It known to be working with all the major browsers on all available platforms (Win/Mac/Linux) @@ -51,43 +36,10 @@ It known to be working with all the major browsers on all available platforms (W * Safari-3 * Chrome-0.2 -## Features added in this version (0.7.x) - * Implemented as $.fn - let you use `this`. - * jQuery selectors are supported. - * Extending `$.fn.bind` and `$.fn.unbind` so you get a single interface for binding events to handlers - -## Overriding jQuery -The plugin wraps the following jQuery methods: - * $.fn.bind - * $.fn.unbind - * $.find - -Even though the plugin overrides these methods, the original methods will *always* be called. - -The plugin will add functionality only for the `keydown`, `keyup` and `keypress` event types. Any other types are passed untouched to the original `'bind()'` and `'unbind()'` methods. - -Moreover, if you call `bind()` without passing the shortcut key combination e.g. `$(document).bind('keydown', fn)` only the original `'bind()'` method will be executed. - -I also modified the `$.fn.find` method by adding a single line at the top of the function body. here is the code: - -
-    jQuery.fn.find = function( selector ) {
-        // the line I added
-        this.query=selector;
-        // call jQuery original find
-        return jQuery.fn.__find__.apply(this, arguments);
-    };
-
- -You can read about this at [jQuery's User Group](http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d) - -###Notes +### Addendum Firefox is the most liberal one in the manner of letting you capture all short-cuts even those that are built-in in the browser such as `Ctrl-t` for new tab, or `Ctrl-a` for selecting all text. You can always bubble them up to the browser by returning `true` in your handler. Others, (IE) either let you handle built-in short-cuts, but will add their functionality after your code has executed. Or (Opera/Safari) will *not* pass those events to the DOM at all. *So, if you bind `Ctrl-Q` or `Alt-F4` and your Safari/Opera window is closed don't be surprised.* - - -###Current Version is: beta 0.7 \ No newline at end of file diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 9456690..fbd71c7 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -1,250 +1,99 @@ /* -(c) Copyrights 2007 - 2008 + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ +*/ -Original idea by by Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ - -jQuery Plugin by Tzury Bar Yochay -tzury.by@gmail.com -http://evalinux.wordpress.com -http://facebook.com/profile.php?id=513676303 +(function(jQuery){ + + jQuery.hotkeys = { + version: "0.8", -Project's sites: -http://code.google.com/p/js-hotkeys/ -http://github.com/tzuryby/hotkeys/tree/master + specialKeys: { + 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + }, + + shiftNums: { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + } + }; -License: same as jQuery license. + function keyHandler( handleObj ) { + // Only care when a possible input has been specified + if ( typeof handleObj.data !== "string" ) { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "); + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + event.target.type === "text") ) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode( event.which ).toLowerCase(), + key, modif = "", possible = {}; -USAGE: - // simple usage - $(document).bind('keydown', 'Ctrl+c', function(){ alert('copy anyone?');}); - - // special options such as disableInIput - $(document).bind('keydown', {combi:'Ctrl+x', disableInInput: true} , function() {}); - -Note: - This plugin wraps the following jQuery methods: $.fn.find, $.fn.bind and $.fn.unbind -*/ + // check combinations (alt|ctrl|shift+anything) + if ( event.altKey && special !== "alt" ) { + modif += "alt+"; + } -(function (jQuery){ - // keep reference to the original $.fn.bind, $.fn.unbind and $.fn.find - if (jQuery.fn.__bind__ === undefined){ - jQuery.fn.__bind__ = jQuery.fn.bind; - } - if (jQuery.fn.__unbind__ === undefined){ - jQuery.fn.__unbind__ = jQuery.fn.unbind; - } - if (jQuery.fn.__find__ === undefined){ - jQuery.fn.__find__ = jQuery.fn.find; - } - - var hotkeys = { - version: '0.7.9', - override: /keypress|keydown|keyup/g, - triggersMap: {}, - - specialKeys: { 27: 'esc', 9: 'tab', 32:'space', 13: 'return', 8:'backspace', 145: 'scroll', - 20: 'capslock', 144: 'numlock', 19:'pause', 45:'insert', 36:'home', 46:'del', - 35:'end', 33: 'pageup', 34:'pagedown', 37:'left', 38:'up', 39:'right',40:'down', - 109: '-', - 112:'f1',113:'f2', 114:'f3', 115:'f4', 116:'f5', 117:'f6', 118:'f7', 119:'f8', - 120:'f9', 121:'f10', 122:'f11', 123:'f12', 191: '/'}, - - shiftNums: { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&", - "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<", - ".":">", "/":"?", "\\":"|" }, - - newTrigger: function (type, combi, callback) { - // i.e. {'keyup': {'ctrl': {cb: callback, disableInInput: false}}} - var result = {}; - result[type] = {}; - result[type][combi] = {cb: callback, disableInInput: false, shortcut:combi}; - return result; - } - }; - // add firefox num pad char codes - //if (jQuery.browser.mozilla){ - // add num pad char codes - hotkeys.specialKeys = jQuery.extend(hotkeys.specialKeys, { 96: '0', 97:'1', 98: '2', 99: - '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9', 106: '*', - 107: '+', 109: '-', 110: '.', 111 : '/' - }); - //} - - // a wrapper around of $.fn.find - // see more at: http://groups.google.com/group/jquery-en/browse_thread/thread/18f9825e8d22f18d - jQuery.fn.find = function( selector ) { - this.query = selector; - return jQuery.fn.__find__.apply(this, arguments); - }; - - jQuery.fn.unbind = function (type, combi, fn){ - if (jQuery.isFunction(combi)){ - fn = combi; - combi = null; - } - if (combi && typeof combi === 'string'){ - var selectorId = ((this.prevObject && this.prevObject.query) || (this[0].id && this[0].id) || this[0]).toString(); - var hkTypes = type.split(' '); - for (var x=0; xTest #02 $(document).ready(function(){ $(document).bind('keydown', 'ctrl+l', function(){$('#input_01')[0].focus();}) .bind('keydown', 'shift+#', function(){$('#input_01')[0].value = "Shift#";}) - .bind('keyup', 'a', function(event){ - var v = $('#input_01')[0].value; - v = v.replace("a", "b"); - v = $('#input_01')[0].value = v; - - }) //.bind('keyup', function () { alert (arguments); }) .bind('click', function (event){ if (event.target == $('html')[0]){ alert("save the planet, don't waste energy over meaningless clicking"); } }); - $('input.foo').bind( - 'keydown', {combi:'ctrl+k', disableInInput: false}, function(event){ - log('binding keydown/ctrl+k to input applied on #' + event.target.id + ''); - event.stopPropagation(); - event.preventDefault(); - } - ); + + $('#input_01').bind('keyup', 'a', function(event){ + this.value = this.value.replace(/a/g, "b"); + }); + + $('input.foo').bind('keydown', 'ctrl+k', function(event){ + log('binding keydown/ctrl+k to input applied on #' + event.target.id + ''); + return false; + }); + $('table').bind('keydown click keyup', 'ctrl+l', clickHandler); }); function clickHandler(event){ log('binding ' + event.type + ' with(ctrl+l) to table applied on #' + event.target.id + ''); - event.stopPropagation(); - event.preventDefault(); + return false; } function unbindClick(){ diff --git a/test-static-04.html b/test-static-04.html index c2cc996..470edd0 100644 --- a/test-static-04.html +++ b/test-static-04.html @@ -16,7 +16,7 @@ alert("Hello Slash"); return false; }); - jQuery(document).bind('keydown', 'ctrl+p', function (evt){ + jQuery(document).bind('keydown', 'ctrl+p meta+p', function (evt){ alert("think green-don't print"); return false; }); From a1bd243e5bf23f4b586afc15397843678c5bca99 Mon Sep 17 00:00:00 2001 From: Kevin Gorski Date: Fri, 26 Nov 2010 13:28:56 -0700 Subject: [PATCH 03/59] Included password and HTML5 input types in default ignore list --- jquery.hotkeys.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index fbd71c7..5905f9d 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -39,12 +39,13 @@ } var origHandler = handleObj.handler, - keys = handleObj.data.toLowerCase().split(" "); + keys = handleObj.data.toLowerCase().split(" "), + textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; handleObj.handler = function( event ) { // Don't fire in text-accepting inputs that we didn't directly bind to if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || - event.target.type === "text") ) { + jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) { return; } From a63c714b4713e7cf130e0ed60e92e36f7ff2b308 Mon Sep 17 00:00:00 2001 From: Steven Skoczen Date: Tue, 21 Jun 2011 15:04:03 -0700 Subject: [PATCH 04/59] Updated the link to Tzury's hotkeys instead of a 404. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22873d6..8aafc17 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ #About **jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. -This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](http://github.com/tzuryby/hotkeys) +This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](https://github.com/tzuryby/jquery.hotkeys) The syntax is as follows: From 2fa06e2899a49246f6e514d90cc6e527518363f5 Mon Sep 17 00:00:00 2001 From: Tutkun Date: Fri, 21 Sep 2012 00:51:35 +0300 Subject: [PATCH 05/59] Update - foreach for bind! the short code --- test-static-01.html | 277 ++++++-------------------------------------- 1 file changed, 38 insertions(+), 239 deletions(-) diff --git a/test-static-01.html b/test-static-01.html index 7385e12..eee8fd4 100644 --- a/test-static-01.html +++ b/test-static-01.html @@ -12,247 +12,46 @@ //This page is a result of an autogenerated content made by running test.html with firefox. function domo(){ jQuery('#platform-details').html('' + navigator.userAgent + ''); - jQuery(document).bind('keydown', 'esc',function (evt){jQuery('#_esc').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'tab',function (evt){jQuery('#_tab').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'space',function (evt){jQuery('#_space').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'return',function (evt){jQuery('#_return').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'backspace',function (evt){jQuery('#_backspace').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'scroll',function (evt){jQuery('#_scroll').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'capslock',function (evt){jQuery('#_capslock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'numlock',function (evt){jQuery('#_numlock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'pause',function (evt){jQuery('#_pause').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'insert',function (evt){jQuery('#_insert').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'home',function (evt){jQuery('#_home').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'del',function (evt){jQuery('#_del').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'end',function (evt){jQuery('#_end').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'pageup',function (evt){jQuery('#_pageup').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'pagedown',function (evt){jQuery('#_pagedown').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'left',function (evt){jQuery('#_left').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'up',function (evt){jQuery('#_up').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'right',function (evt){jQuery('#_right').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'down',function (evt){jQuery('#_down').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f1',function (evt){jQuery('#_f1').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f2',function (evt){jQuery('#_f2').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f3',function (evt){jQuery('#_f3').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f4',function (evt){jQuery('#_f4').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f5',function (evt){jQuery('#_f5').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f6',function (evt){jQuery('#_f6').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f7',function (evt){jQuery('#_f7').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f8',function (evt){jQuery('#_f8').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f9',function (evt){jQuery('#_f9').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f10',function (evt){jQuery('#_f10').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f11',function (evt){jQuery('#_f11').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f12',function (evt){jQuery('#_f12').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '1',function (evt){jQuery('#_1').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '2',function (evt){jQuery('#_2').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '3',function (evt){jQuery('#_3').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '4',function (evt){jQuery('#_4').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '5',function (evt){jQuery('#_5').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '6',function (evt){jQuery('#_6').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '7',function (evt){jQuery('#_7').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '8',function (evt){jQuery('#_8').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '9',function (evt){jQuery('#_9').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', '0',function (evt){jQuery('#_0').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'a',function (evt){jQuery('#_a').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'b',function (evt){jQuery('#_b').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'c',function (evt){jQuery('#_c').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'd',function (evt){jQuery('#_d').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'e',function (evt){jQuery('#_e').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'f',function (evt){jQuery('#_f').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'g',function (evt){jQuery('#_g').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'h',function (evt){jQuery('#_h').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'i',function (evt){jQuery('#_i').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'j',function (evt){jQuery('#_j').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'k',function (evt){jQuery('#_k').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'l',function (evt){jQuery('#_l').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'm',function (evt){jQuery('#_m').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'n',function (evt){jQuery('#_n').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'o',function (evt){jQuery('#_o').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'p',function (evt){jQuery('#_p').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'q',function (evt){jQuery('#_q').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'r',function (evt){jQuery('#_r').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 's',function (evt){jQuery('#_s').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 't',function (evt){jQuery('#_t').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'u',function (evt){jQuery('#_u').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'v',function (evt){jQuery('#_v').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'w',function (evt){jQuery('#_w').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'x',function (evt){jQuery('#_x').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'y',function (evt){jQuery('#_y').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'z',function (evt){jQuery('#_z').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+a',function (evt){jQuery('#_Ctrl_a').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+b',function (evt){jQuery('#_Ctrl_b').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+c',function (evt){jQuery('#_Ctrl_c').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+d',function (evt){jQuery('#_Ctrl_d').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+e',function (evt){jQuery('#_Ctrl_e').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f',function (evt){jQuery('#_Ctrl_f').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+g',function (evt){jQuery('#_Ctrl_g').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+h',function (evt){jQuery('#_Ctrl_h').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+i',function (evt){jQuery('#_Ctrl_i').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+j',function (evt){jQuery('#_Ctrl_j').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+k',function (evt){jQuery('#_Ctrl_k').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+l',function (evt){jQuery('#_Ctrl_l').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+m',function (evt){jQuery('#_Ctrl_m').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+n',function (evt){jQuery('#_Ctrl_n').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+o',function (evt){jQuery('#_Ctrl_o').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+p',function (evt){jQuery('#_Ctrl_p').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+q',function (evt){jQuery('#_Ctrl_q').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+r',function (evt){jQuery('#_Ctrl_r').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+s',function (evt){jQuery('#_Ctrl_s').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+t',function (evt){jQuery('#_Ctrl_t').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+u',function (evt){jQuery('#_Ctrl_u').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+v',function (evt){jQuery('#_Ctrl_v').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+w',function (evt){jQuery('#_Ctrl_w').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+x',function (evt){jQuery('#_Ctrl_x').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+y',function (evt){jQuery('#_Ctrl_y').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+z',function (evt){jQuery('#_Ctrl_z').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+a',function (evt){jQuery('#_Shift_a').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+b',function (evt){jQuery('#_Shift_b').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+c',function (evt){jQuery('#_Shift_c').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+d',function (evt){jQuery('#_Shift_d').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+e',function (evt){jQuery('#_Shift_e').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f',function (evt){jQuery('#_Shift_f').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+g',function (evt){jQuery('#_Shift_g').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+h',function (evt){jQuery('#_Shift_h').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+i',function (evt){jQuery('#_Shift_i').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+j',function (evt){jQuery('#_Shift_j').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+k',function (evt){jQuery('#_Shift_k').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+l',function (evt){jQuery('#_Shift_l').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+m',function (evt){jQuery('#_Shift_m').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+n',function (evt){jQuery('#_Shift_n').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+o',function (evt){jQuery('#_Shift_o').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+p',function (evt){jQuery('#_Shift_p').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+q',function (evt){jQuery('#_Shift_q').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+r',function (evt){jQuery('#_Shift_r').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+s',function (evt){jQuery('#_Shift_s').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+t',function (evt){jQuery('#_Shift_t').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+u',function (evt){jQuery('#_Shift_u').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+v',function (evt){jQuery('#_Shift_v').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+w',function (evt){jQuery('#_Shift_w').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+x',function (evt){jQuery('#_Shift_x').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+y',function (evt){jQuery('#_Shift_y').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+z',function (evt){jQuery('#_Shift_z').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+a',function (evt){jQuery('#_Alt_a').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+b',function (evt){jQuery('#_Alt_b').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+c',function (evt){jQuery('#_Alt_c').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+d',function (evt){jQuery('#_Alt_d').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+e',function (evt){jQuery('#_Alt_e').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f',function (evt){jQuery('#_Alt_f').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+g',function (evt){jQuery('#_Alt_g').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+h',function (evt){jQuery('#_Alt_h').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+i',function (evt){jQuery('#_Alt_i').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+j',function (evt){jQuery('#_Alt_j').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+k',function (evt){jQuery('#_Alt_k').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+l',function (evt){jQuery('#_Alt_l').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+m',function (evt){jQuery('#_Alt_m').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+n',function (evt){jQuery('#_Alt_n').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+o',function (evt){jQuery('#_Alt_o').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+p',function (evt){jQuery('#_Alt_p').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+q',function (evt){jQuery('#_Alt_q').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+r',function (evt){jQuery('#_Alt_r').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+s',function (evt){jQuery('#_Alt_s').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+t',function (evt){jQuery('#_Alt_t').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+u',function (evt){jQuery('#_Alt_u').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+v',function (evt){jQuery('#_Alt_v').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+w',function (evt){jQuery('#_Alt_w').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+x',function (evt){jQuery('#_Alt_x').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+y',function (evt){jQuery('#_Alt_y').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+z',function (evt){jQuery('#_Alt_z').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+esc', function (evt){jQuery('#_Ctrl_esc').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+tab', function (evt){jQuery('#_Ctrl_tab').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+space', function (evt){jQuery('#_Ctrl_space').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+return', function (evt){jQuery('#_Ctrl_return').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+backspace', function (evt){jQuery('#_Ctrl_backspace').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+scroll', function (evt){jQuery('#_Ctrl_scroll').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+capslock', function (evt){jQuery('#_Ctrl_capslock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+numlock', function (evt){jQuery('#_Ctrl_numlock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+pause', function (evt){jQuery('#_Ctrl_pause').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+insert', function (evt){jQuery('#_Ctrl_insert').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+home', function (evt){jQuery('#_Ctrl_home').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+del', function (evt){jQuery('#_Ctrl_del').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+end', function (evt){jQuery('#_Ctrl_end').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+pageup', function (evt){jQuery('#_Ctrl_pageup').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+pagedown', function (evt){jQuery('#_Ctrl_pagedown').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+left', function (evt){jQuery('#_Ctrl_left').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+up', function (evt){jQuery('#_Ctrl_up').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+right', function (evt){jQuery('#_Ctrl_right').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+down', function (evt){jQuery('#_Ctrl_down').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f1', function (evt){jQuery('#_Ctrl_f1').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f2', function (evt){jQuery('#_Ctrl_f2').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f3', function (evt){jQuery('#_Ctrl_f3').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f4', function (evt){jQuery('#_Ctrl_f4').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f5', function (evt){jQuery('#_Ctrl_f5').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f6', function (evt){jQuery('#_Ctrl_f6').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f7', function (evt){jQuery('#_Ctrl_f7').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f8', function (evt){jQuery('#_Ctrl_f8').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f9', function (evt){jQuery('#_Ctrl_f9').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f10', function (evt){jQuery('#_Ctrl_f10').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f11', function (evt){jQuery('#_Ctrl_f11').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Ctrl+f12', function (evt){jQuery('#_Ctrl_f12').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+esc', function (evt){jQuery('#_Shift_esc').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+tab', function (evt){jQuery('#_Shift_tab').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+space', function (evt){jQuery('#_Shift_space').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+return', function (evt){jQuery('#_Shift_return').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+backspace', function (evt){jQuery('#_Shift_backspace').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+scroll', function (evt){jQuery('#_Shift_scroll').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+capslock', function (evt){jQuery('#_Shift_capslock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+numlock', function (evt){jQuery('#_Shift_numlock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+pause', function (evt){jQuery('#_Shift_pause').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+insert', function (evt){jQuery('#_Shift_insert').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+home', function (evt){jQuery('#_Shift_home').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+del', function (evt){jQuery('#_Shift_del').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+end', function (evt){jQuery('#_Shift_end').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+pageup', function (evt){jQuery('#_Shift_pageup').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+pagedown', function (evt){jQuery('#_Shift_pagedown').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+left', function (evt){jQuery('#_Shift_left').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+up', function (evt){jQuery('#_Shift_up').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+right', function (evt){jQuery('#_Shift_right').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+down', function (evt){jQuery('#_Shift_down').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f1', function (evt){jQuery('#_Shift_f1').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f2', function (evt){jQuery('#_Shift_f2').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f3', function (evt){jQuery('#_Shift_f3').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f4', function (evt){jQuery('#_Shift_f4').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f5', function (evt){jQuery('#_Shift_f5').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f6', function (evt){jQuery('#_Shift_f6').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f7', function (evt){jQuery('#_Shift_f7').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f8', function (evt){jQuery('#_Shift_f8').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f9', function (evt){jQuery('#_Shift_f9').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f10', function (evt){jQuery('#_Shift_f10').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f11', function (evt){jQuery('#_Shift_f11').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Shift+f12', function (evt){jQuery('#_Shift_f12').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+esc', function (evt){jQuery('#_Alt_esc').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+tab', function (evt){jQuery('#_Alt_tab').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+space', function (evt){jQuery('#_Alt_space').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+return', function (evt){jQuery('#_Alt_return').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+backspace', function (evt){jQuery('#_Alt_backspace').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+scroll', function (evt){jQuery('#_Alt_scroll').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+capslock', function (evt){jQuery('#_Alt_capslock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+numlock', function (evt){jQuery('#_Alt_numlock').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+pause', function (evt){jQuery('#_Alt_pause').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+insert', function (evt){jQuery('#_Alt_insert').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+home', function (evt){jQuery('#_Alt_home').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+del', function (evt){jQuery('#_Alt_del').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+end', function (evt){jQuery('#_Alt_end').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+pageup', function (evt){jQuery('#_Alt_pageup').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+pagedown', function (evt){jQuery('#_Alt_pagedown').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+left', function (evt){jQuery('#_Alt_left').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+up', function (evt){jQuery('#_Alt_up').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+right', function (evt){jQuery('#_Alt_right').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+down', function (evt){jQuery('#_Alt_down').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f1', function (evt){jQuery('#_Alt_f1').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f2', function (evt){jQuery('#_Alt_f2').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f3', function (evt){jQuery('#_Alt_f3').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f4', function (evt){jQuery('#_Alt_f4').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f5', function (evt){jQuery('#_Alt_f5').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f6', function (evt){jQuery('#_Alt_f6').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f7', function (evt){jQuery('#_Alt_f7').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f8', function (evt){jQuery('#_Alt_f8').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f9', function (evt){jQuery('#_Alt_f9').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f10', function (evt){jQuery('#_Alt_f10').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f11', function (evt){jQuery('#_Alt_f11').addClass('dirty'); return false; }); - jQuery(document).bind('keydown', 'Alt+f12', function (evt){jQuery('#_Alt_f12').addClass('dirty'); return false; }); + + var elements = [ + "esc","tab","space","return","backspace","scroll","capslock","numlock","insert","home","del","end","pageup","pagedown", + "left","up","right","down", + "f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12", + "1","2","3","4","5","6","7","8","9","0", + "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z", + "Ctrl+a","Ctrl+b","Ctrl+c","Ctrl+d","Ctrl+e","Ctrl+f","Ctrl+g","Ctrl+h","Ctrl+i","Ctrl+j","Ctrl+k","Ctrl+l","Ctrl+m", + "Ctrl+n","Ctrl+o","Ctrl+p","Ctrl+q","Ctrl+r","Ctrl+s","Ctrl+t","Ctrl+u","Ctrl+v","Ctrl+w","Ctrl+x","Ctrl+y","Ctrl+z", + "Shift+a","Shift+b","Shift+c","Shift+d","Shift+e","Shift+f","Shift+g","Shift+h","Shift+i","Shift+j","Shift+k","Shift+l", + "Shift+m","Shift+n","Shift+o","Shift+p","Shift+q","Shift+r","Shift+s","Shift+t","Shift+u","Shift+v","Shift+w","Shift+x", + "Shift+y","Shift+z", + "Alt+a","Alt+b","Alt+c","Alt+d","Alt+e","Alt+f","Alt+g","Alt+h","Alt+i","Alt+j","Alt+k","Alt+l", + "Alt+m","Alt+n","Alt+o","Alt+p","Alt+q","Alt+r","Alt+s","Alt+t","Alt+u","Alt+v","Alt+w","Alt+x","Alt+y","Alt+z", + "Ctrl+esc","Ctrl+tab","Ctrl+space","Ctrl+return","Ctrl+backspace","Ctrl+scroll","Ctrl+capslock","Ctrl+numlock", + "Ctrl+insert","Ctrl+home","Ctrl+del","Ctrl+end","Ctrl+pageup","Ctrl+pagedown","Ctrl+left","Ctrl+up","Ctrl+right", + "Ctrl+down", + "Ctrl+f1","Ctrl+f2","Ctrl+f3","Ctrl+f4","Ctrl+f5","Ctrl+f6","Ctrl+f7","Ctrl+f8","Ctrl+f9","Ctrl+f10","Ctrl+f11","Ctrl+f12", + "Shift+esc","Shift+tab","Shift+space","Shift+return","Shift+backspace","Shift+scroll","Shift+capslock","Shift+numlock", + "Shift+insert","Shift+home","Shift+del","Shift+end","Shift+pageup","Shift+pagedown","Shift+left","Shift+up", + "Shift+right","Shift+down", + "Shift+f1","Shift+f2","Shift+f3","Shift+f4","Shift+f5","Shift+f6","Shift+f7","Shift+f8","Shift+f9","Shift+f10","Shift+f11","Shift+f12", + "Alt+esc","Alt+tab","Alt+space","Alt+return","Alt+backspace","Alt+scroll","Alt+capslock","Alt+numlock", + "Alt+insert","Alt+home","Alt+del","Alt+end","Alt+pageup","Alt+pagedown","Alt+left","Alt+up","Alt+right","Alt+down", + "Alt+f1","Alt+f2","Alt+f3","Alt+f4","Alt+f5","Alt+f6","Alt+f7","Alt+f8","Alt+f9","Alt+f10","Alt+f11","Alt+f12" + ]; + + // the fetching... + $.each(elements, function(i, e) { // i is element index. e is element as text. + var newElment = ( /[\+]+/.test(elements[i]) ) ? elements[i].replace("+","_") : elements[i]; + + // Binding keys + $(document).bind('keydown', elements[i], function assets() { + $('#_'+ newElement).addClass("dirty"); + return false; + }); + }); + } - jQuery(document).ready(domo); From d73131df8a3a1a9cdfd6e5e0f740d7773d46702c Mon Sep 17 00:00:00 2001 From: John Resig Date: Thu, 17 Jan 2013 10:57:44 -0500 Subject: [PATCH 06/59] Add in jQuery plugin manifest. --- hotkeys.jquery.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 hotkeys.jquery.json diff --git a/hotkeys.jquery.json b/hotkeys.jquery.json new file mode 100644 index 0000000..d022a7e --- /dev/null +++ b/hotkeys.jquery.json @@ -0,0 +1,36 @@ +{ + "name": "hotkeys", + "title": "jQuery Hotkeys", + "description": "jQuery Hotkeys lets you watch for keyboard events anywhere in your code supporting almost any key combination.", + "keywords": [ + "keyboard", + "events" + ], + "version": "0.1.0", + "author": { + "name": "John Resig", + "url": "https://ejohn.org/" + }, + "maintainers": [ + { + "name": "John Resig", + "email": "jeresig@gmail.com", + "url": "http://ejohn.org/" + } + ], + "licenses": [ + { + "type": "MIT", + "url": "http://opensource.org/licenses/MIT" + }, + { + "type": "GPLv2", + "url": "http://www.gnu.org/licenses/gpl-2.0.html" + } + ], + "homepage": "https://github.com/jeresig/jquery.hotkeys", + "docs": "https://github.com/jeresig/jquery.hotkeys", + "dependencies": { + "jquery": ">=1.4.2" + } +} \ No newline at end of file From 657710dfd6780f1f81921fdcd0d9d544a28e3190 Mon Sep 17 00:00:00 2001 From: "Evgeny E. Neverov" Date: Sun, 17 Mar 2013 14:00:03 +0900 Subject: [PATCH 07/59] One small change is: now keys are passed by object { keys: '...' } Might be useful, when you want to pass some other data to your handler --- jquery.hotkeys.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 5905f9d..cd23f94 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -10,6 +10,11 @@ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ */ +/* + * One small change is: now keys are passed by object { keys: '...' } + * Might be useful, when you want to pass some other data to your handler + */ + (function(jQuery){ jQuery.hotkeys = { @@ -34,12 +39,12 @@ function keyHandler( handleObj ) { // Only care when a possible input has been specified - if ( typeof handleObj.data !== "string" ) { + if ( !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { return; } - + var origHandler = handleObj.handler, - keys = handleObj.data.toLowerCase().split(" "), + keys = handleObj.data.keys.toLowerCase().split(" "), textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; handleObj.handler = function( event ) { From 90bf64c54634ecf849c93ab4625fc6320b91a03a Mon Sep 17 00:00:00 2001 From: "Evgeny E. Neverov" Date: Sun, 17 Mar 2013 14:01:59 +0900 Subject: [PATCH 08/59] Backward compatibility --- jquery.hotkeys.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index cd23f94..f99ab74 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -38,6 +38,10 @@ }; function keyHandler( handleObj ) { + if ( typeof handleObj.data === "string" ) { + handleObj.data = { keys: handleObj.data }; + } + // Only care when a possible input has been specified if ( !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { return; From dacd92fdcec8c4a31ae76da024e9de64f2967fed Mon Sep 17 00:00:00 2001 From: Billy Onjea Date: Mon, 18 Mar 2013 16:36:57 -0300 Subject: [PATCH 09/59] Updated hotKey README with an example using jQuery's on & off methods --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 22873d6..77dab48 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,21 @@ The syntax is as follows: $('input.foo').bind('keyup', '$', function(){ this.value = this.value.replace('$', 'EUR'); }); + +Syntax when wanting to use jQuery's `on()`/`off` methods: + + $(expression).on(types, null, keys, handler); + $(expression).off(types, handler); + + $(document).on('keydown', null, 'ctrl+a', fn); + + // e.g. replace '$' sign with 'EUR' + $('input.foo').on('keyup', null, '$', function(){ + this.value = this.value.replace('$', 'EUR'); + }); ## Types -Supported types are `'keydown'`, `'keyup'` and `'keypress'` +Supported types are `'keydown'`, `'keyup'` and `'keypress'` ## Notes From 189baa381cb57b50dbeea771c954afdaea1ffdff Mon Sep 17 00:00:00 2001 From: Gabriel Florit Date: Sun, 31 Mar 2013 03:00:05 -0400 Subject: [PATCH 10/59] added three special keys: ; ' \ --- jquery.hotkeys.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 5905f9d..ca179c8 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -22,7 +22,8 @@ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/", + 220: "\\", 222: "'", 224: "meta" }, shiftNums: { From a363591f54c4db44cbf121ad86ea27802be04490 Mon Sep 17 00:00:00 2001 From: CalvinChen Date: Wed, 1 May 2013 15:32:25 +0800 Subject: [PATCH 11/59] fix bugs. --- jquery.hotkeys.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 34b5b7d..6a7e52e 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -44,7 +44,7 @@ } // Only care when a possible input has been specified - if ( !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { + if ( !handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { return; } @@ -107,4 +107,4 @@ jQuery.event.special[ this ] = { add: keyHandler }; }); -})( jQuery ); \ No newline at end of file +})( jQuery ); From 18ec5611f628057b94e1c1d48a7bbbe7ad6272cd Mon Sep 17 00:00:00 2001 From: Yury Petrov Date: Mon, 13 May 2013 03:33:00 +0600 Subject: [PATCH 12/59] add keyCode 10 for return it is triggered by IE when pressing Ctrl+Return --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 34b5b7d..d4e355f 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -21,7 +21,7 @@ version: "0.8", specialKeys: { - 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", From c52aae2dbc6db9270604eb3cbdcbc133d16ad2d5 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 1 May 2013 09:09:52 +0300 Subject: [PATCH 13/59] small syntax fix --- test-static-01.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-static-01.html b/test-static-01.html index eee8fd4..24fd13f 100644 --- a/test-static-01.html +++ b/test-static-01.html @@ -41,7 +41,7 @@ // the fetching... $.each(elements, function(i, e) { // i is element index. e is element as text. - var newElment = ( /[\+]+/.test(elements[i]) ) ? elements[i].replace("+","_") : elements[i]; + var newElement = ( /[\+]+/.test(elements[i]) ) ? elements[i].replace("+","_") : elements[i]; // Binding keys $(document).bind('keydown', elements[i], function assets() { From f8225d120c7bce1e92130ad2ee8a32a3973c395e Mon Sep 17 00:00:00 2001 From: Tim Branyen Date: Sat, 25 May 2013 14:07:35 -0400 Subject: [PATCH 14/59] added in grunt for linting, eventually testing --- Gruntfile.js | 16 ++++++++++++++++ package.json | 15 +++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Gruntfile.js create mode 100644 package.json diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..1382e47 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,16 @@ +module.exports = function(grunt) { + + // Configuration. + grunt.initConfig({ + jshint: { + files: ["jquery.hotkeys.js"] + } + }); + + // Task loading. + grunt.loadNpmTasks("grunt-contrib-jshint"); + + // Task registration. + grunt.registerTask("default", ["jshint"]); + +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..8d9758c --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "jquery.hotkeys", + "version": "0.1.0", + "description": "**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.", + "main": "jquery.hotkeys.js", + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-jshint": "~0.5.4" + }, + "repository": { + "type": "git", + "url": "https://github.com/jeresig/jquery.hotkeys" + }, + "license": "MIT or GPL Version 2" +} From 2fdc8c11a1c602ad8ed16b67c3c0503c9b4c4fe6 Mon Sep 17 00:00:00 2001 From: Tim Branyen Date: Sat, 25 May 2013 14:12:44 -0400 Subject: [PATCH 15/59] added tel input type --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 6a7e52e..da2ff9e 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -50,7 +50,7 @@ var origHandler = handleObj.handler, keys = handleObj.data.keys.toLowerCase().split(" "), - textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; + textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel"]; handleObj.handler = function( event ) { // Don't fire in text-accepting inputs that we didn't directly bind to From 4ac39a76a18d5c0536fa3d5741c0fc4d65911b33 Mon Sep 17 00:00:00 2001 From: Tim Branyen Date: Sat, 25 May 2013 14:22:32 -0400 Subject: [PATCH 16/59] Removed unused variable key per @YuppY, updated the jQuery reference to be on the global scope, updated linting to catch these issues. --- Gruntfile.js | 5 +++++ jquery.hotkeys.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1382e47..5dc832b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,6 +3,11 @@ module.exports = function(grunt) { // Configuration. grunt.initConfig({ jshint: { + options: { + "undef": true, + "unused": true + }, + files: ["jquery.hotkeys.js"] } }); diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index f088f75..867801e 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -62,7 +62,7 @@ // Keypress represents characters, not special keys var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], character = String.fromCharCode( event.which ).toLowerCase(), - key, modif = "", possible = {}; + modif = "", possible = {}; // check combinations (alt|ctrl|shift+anything) if ( event.altKey && special !== "alt" ) { @@ -107,4 +107,4 @@ jQuery.event.special[ this ] = { add: keyHandler }; }); -})( jQuery ); +})( this.jQuery ); From 8b73fdc804b26f69b4636634f0873c1ea2443231 Mon Sep 17 00:00:00 2001 From: Yury Petrov Date: Mon, 13 May 2013 02:43:04 +0600 Subject: [PATCH 17/59] fix keypress on special keys and other events on characters --- jquery.hotkeys.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 867801e..c240a7f 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -58,10 +58,10 @@ jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) { return; } - - // Keypress represents characters, not special keys - var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], - character = String.fromCharCode( event.which ).toLowerCase(), + + var special = jQuery.hotkeys.specialKeys[ event.keyCode ], + // character codes are available only in keypress + character = event.type === "keypress" && String.fromCharCode( event.which ).toLowerCase(), modif = "", possible = {}; // check combinations (alt|ctrl|shift+anything) @@ -84,8 +84,9 @@ if ( special ) { possible[ modif + special ] = true; + } - } else { + if ( character ) { possible[ modif + character ] = true; possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; From 3cae520caf617f7c3337a18251e69f58d9755a5a Mon Sep 17 00:00:00 2001 From: Yury Petrov Date: Sun, 9 Jun 2013 21:12:39 +0600 Subject: [PATCH 18/59] restore keyup/keydown for character keys --- jquery.hotkeys.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index c240a7f..a060fc0 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -60,8 +60,7 @@ } var special = jQuery.hotkeys.specialKeys[ event.keyCode ], - // character codes are available only in keypress - character = event.type === "keypress" && String.fromCharCode( event.which ).toLowerCase(), + character = String.fromCharCode( event.which ).toLowerCase(), modif = "", possible = {}; // check combinations (alt|ctrl|shift+anything) From 87639b5825a6f52bb63291754651b41ae5929ee3 Mon Sep 17 00:00:00 2001 From: nicoder Date: Fri, 11 Oct 2013 05:47:23 +0200 Subject: [PATCH 19/59] minor edit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b692ad..2620b3d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Hotkeys aren't tracked if you're inside of an input element (unless you explicit Works with jQuery 1.4.2 and newer. -It known to be working with all the major browsers on all available platforms (Win/Mac/Linux) +It is known to be working with all the major browsers on all available platforms (Win/Mac/Linux) * IE 6/7/8 * FF 1.5/2/3 From 599bff2e7be9d1e55c3494ac8ca7b0657f8a8cfc Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Mon, 3 Feb 2014 09:54:20 +1100 Subject: [PATCH 20/59] Adding Grunt with jasmine + jshint tasks and some basic tests, updated package.json and added Travis build script hook for jasmine + status to README.md --- .gitignore | 2 + Gruntfile.js | 15 +- README.md | 2 + package.json | 3 +- test/SpecRunner.html | 28 + test/lib/boot.js | 181 ++ test/lib/console.js | 160 ++ test/lib/jasmine-html.js | 359 +++ test/lib/jasmine.css | 55 + test/lib/jasmine.js | 2402 +++++++++++++++++++ test/lib/jasmine_favicon.png | Bin 0 -> 2057 bytes test/lib/sinon.js | 4299 ++++++++++++++++++++++++++++++++++ test/lib/underscore.js | 6 + test/spec/bindingSpec.js | 69 + travis.yml | 7 + 15 files changed, 7586 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 test/SpecRunner.html create mode 100644 test/lib/boot.js create mode 100644 test/lib/console.js create mode 100644 test/lib/jasmine-html.js create mode 100644 test/lib/jasmine.css create mode 100644 test/lib/jasmine.js create mode 100644 test/lib/jasmine_favicon.png create mode 100644 test/lib/sinon.js create mode 100644 test/lib/underscore.js create mode 100644 test/spec/bindingSpec.js create mode 100644 travis.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb79dd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.idea diff --git a/Gruntfile.js b/Gruntfile.js index 5dc832b..ed36999 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,13 +9,26 @@ module.exports = function(grunt) { }, files: ["jquery.hotkeys.js"] + }, + jasmine: { + pivotal: { + src: 'test/lib/**.js', + options: { + vendor: 'jquery-1.4.2.js', + specs: 'test/spec/*Spec.js' + } + } } }); // Task loading. grunt.loadNpmTasks("grunt-contrib-jshint"); + // tests + grunt.loadNpmTasks('grunt-contrib-jasmine'); + // Task registration. - grunt.registerTask("default", ["jshint"]); + grunt.registerTask("default", ["jshint", "jasmine"]); + }; diff --git a/README.md b/README.md index 2620b3d..898ebcf 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# jQuery.Hotkeys [![Build Status](https://secure.travis-ci.org/jeresig/jquery.hotkeys.png)](http://travis-ci.org/jeresig/jquery.hotkeys) + #About **jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. diff --git a/package.json b/package.json index 8d9758c..43513ac 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "jquery.hotkeys.js", "devDependencies": { "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.5.4" + "grunt-contrib-jshint": "~0.8.0", + "grunt-contrib-jasmine": "~0.6.0" }, "repository": { "type": "git", diff --git a/test/SpecRunner.html b/test/SpecRunner.html new file mode 100644 index 0000000..e5f5603 --- /dev/null +++ b/test/SpecRunner.html @@ -0,0 +1,28 @@ + + + + + Backbone Poller Unit Tests + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/lib/boot.js b/test/lib/boot.js new file mode 100644 index 0000000..ec8baa0 --- /dev/null +++ b/test/lib/boot.js @@ -0,0 +1,181 @@ +/** + Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. + + If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. + + The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. + + [jasmine-gem]: http://github.com/pivotal/jasmine-gem + */ + +(function() { + + /** + * ## Require & Instantiate + * + * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. + */ + window.jasmine = jasmineRequire.core(jasmineRequire); + + /** + * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. + */ + jasmineRequire.html(jasmine); + + /** + * Create the Jasmine environment. This is used to run all specs in a project. + */ + var env = jasmine.getEnv(); + + /** + * ## The Global Interface + * + * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. + */ + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + it: function(desc, func) { + return env.it(desc, func); + }, + + xit: function(desc, func) { + return env.xit(desc, func); + }, + + beforeEach: function(beforeEachFunction) { + return env.beforeEach(beforeEachFunction); + }, + + afterEach: function(afterEachFunction) { + return env.afterEach(afterEachFunction); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + pending: function() { + return env.pending(); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }) + }; + + /** + * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. + */ + if (typeof window == "undefined" && typeof exports == "object") { + extend(exports, jasmineInterface); + } else { + extend(window, jasmineInterface); + } + + /** + * Expose the interface for adding custom equality testers. + */ + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + + /** + * Expose the interface for adding custom expectation matchers + */ + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + /** + * Expose the mock interface for the JavaScript timeout functions + */ + jasmine.clock = function() { + return env.clock; + }; + + /** + * ## Runner Parameters + * + * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. + */ + + var queryString = new jasmine.QueryString({ + getWindowLocation: function() { return window.location; } + }); + + var catchingExceptions = queryString.getParam("catch"); + env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + + /** + * ## Reporters + * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). + */ + var htmlReporter = new jasmine.HtmlReporter({ + env: env, + onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, + getContainer: function() { return document.body; }, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); }, + timer: new jasmine.Timer() + }); + + /** + * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. + */ + env.addReporter(jasmineInterface.jsApiReporter); + env.addReporter(htmlReporter); + + /** + * Filter which specs will be run by matching the start of the full name against the `spec` query param. + */ + var specFilter = new jasmine.HtmlSpecFilter({ + filterString: function() { return queryString.getParam("spec"); } + }); + + env.specFilter = function(spec) { + return specFilter.matches(spec.getFullName()); + }; + + /** + * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. + */ + window.setTimeout = window.setTimeout; + window.setInterval = window.setInterval; + window.clearTimeout = window.clearTimeout; + window.clearInterval = window.clearInterval; + + /** + * ## Execution + * + * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. + */ + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + htmlReporter.initialize(); + env.execute(); + }; + + /** + * Helper function for readability above. + */ + function extend(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; + } + +}()); diff --git a/test/lib/console.js b/test/lib/console.js new file mode 100644 index 0000000..33c1698 --- /dev/null +++ b/test/lib/console.js @@ -0,0 +1,160 @@ +/* +Copyright (c) 2008-2013 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. +*/ +function getJasmineRequireObj() { + if (typeof module !== "undefined" && module.exports) { + return exports; + } else { + window.jasmineRequire = window.jasmineRequire || {}; + return window.jasmineRequire; + } +} + +getJasmineRequireObj().console = function(jRequire, j$) { + j$.ConsoleReporter = jRequire.ConsoleReporter(); +}; + +getJasmineRequireObj().ConsoleReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function ConsoleReporter(options) { + var print = options.print, + showColors = options.showColors || false, + onComplete = options.onComplete || function() {}, + timer = options.timer || noopTimer, + specCount, + failureCount, + failedSpecs = [], + pendingCount, + ansi = { + green: '\x1B[32m', + red: '\x1B[31m', + yellow: '\x1B[33m', + none: '\x1B[0m' + }; + + this.jasmineStarted = function() { + specCount = 0; + failureCount = 0; + pendingCount = 0; + print("Started"); + printNewline(); + timer.start(); + }; + + this.jasmineDone = function() { + printNewline(); + for (var i = 0; i < failedSpecs.length; i++) { + specFailureDetails(failedSpecs[i]); + } + + printNewline(); + var specCounts = specCount + " " + plural("spec", specCount) + ", " + + failureCount + " " + plural("failure", failureCount); + + if (pendingCount) { + specCounts += ", " + pendingCount + " pending " + plural("spec", pendingCount); + } + + print(specCounts); + + printNewline(); + var seconds = timer.elapsed() / 1000; + print("Finished in " + seconds + " " + plural("second", seconds)); + + printNewline(); + + onComplete(failureCount === 0); + }; + + this.specDone = function(result) { + specCount++; + + if (result.status == "pending") { + pendingCount++; + print(colored("yellow", "*")); + return; + } + + if (result.status == "passed") { + print(colored("green", '.')); + return; + } + + if (result.status == "failed") { + failureCount++; + failedSpecs.push(result); + print(colored("red", 'F')); + } + }; + + return this; + + function printNewline() { + print("\n"); + } + + function colored(color, str) { + return showColors ? (ansi[color] + str + ansi.none) : str; + } + + function plural(str, count) { + return count == 1 ? str : str + "s"; + } + + function repeat(thing, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(thing); + } + return arr; + } + + function indent(str, spaces) { + var lines = (str || '').split("\n"); + var newArr = []; + for (var i = 0; i < lines.length; i++) { + newArr.push(repeat(" ", spaces).join("") + lines[i]); + } + return newArr.join("\n"); + } + + function specFailureDetails(result) { + printNewline(); + print(result.fullName); + + for (var i = 0; i < result.failedExpectations.length; i++) { + var failedExpectation = result.failedExpectations[i]; + printNewline(); + print(indent(failedExpectation.stack, 2)); + } + + printNewline(); + } + } + + return ConsoleReporter; +}; diff --git a/test/lib/jasmine-html.js b/test/lib/jasmine-html.js new file mode 100644 index 0000000..985d0d1 --- /dev/null +++ b/test/lib/jasmine-html.js @@ -0,0 +1,359 @@ +/* +Copyright (c) 2008-2013 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. +*/ +jasmineRequire.html = function(j$) { + j$.ResultsNode = jasmineRequire.ResultsNode(); + j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); + j$.QueryString = jasmineRequire.QueryString(); + j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); +}; + +jasmineRequire.HtmlReporter = function(j$) { + + var noopTimer = { + start: function() {}, + elapsed: function() { return 0; } + }; + + function HtmlReporter(options) { + var env = options.env || {}, + getContainer = options.getContainer, + createElement = options.createElement, + createTextNode = options.createTextNode, + onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, + timer = options.timer || noopTimer, + results = [], + specsExecuted = 0, + failureCount = 0, + pendingSpecCount = 0, + htmlReporterMain, + symbols; + + this.initialize = function() { + htmlReporterMain = createDom("div", {className: "html-reporter"}, + createDom("div", {className: "banner"}, + createDom("span", {className: "title"}, "Jasmine"), + createDom("span", {className: "version"}, j$.version) + ), + createDom("ul", {className: "symbol-summary"}), + createDom("div", {className: "alert"}), + createDom("div", {className: "results"}, + createDom("div", {className: "failures"}) + ) + ); + getContainer().appendChild(htmlReporterMain); + + symbols = find(".symbol-summary"); + }; + + var totalSpecsDefined; + this.jasmineStarted = function(options) { + totalSpecsDefined = options.totalSpecsDefined || 0; + timer.start(); + }; + + var summary = createDom("div", {className: "summary"}); + + var topResults = new j$.ResultsNode({}, "", null), + currentParent = topResults; + + this.suiteStarted = function(result) { + currentParent.addChild(result, "suite"); + currentParent = currentParent.last(); + }; + + this.suiteDone = function(result) { + if (currentParent == topResults) { + return; + } + + currentParent = currentParent.parent; + }; + + this.specStarted = function(result) { + currentParent.addChild(result, "spec"); + }; + + var failures = []; + this.specDone = function(result) { + if (result.status != "disabled") { + specsExecuted++; + } + + symbols.appendChild(createDom("li", { + className: result.status, + id: "spec_" + result.id, + title: result.fullName + } + )); + + if (result.status == "failed") { + failureCount++; + + var failure = + createDom("div", {className: "spec-detail failed"}, + createDom("div", {className: "description"}, + createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName) + ), + createDom("div", {className: "messages"}) + ); + var messages = failure.childNodes[1]; + + for (var i = 0; i < result.failedExpectations.length; i++) { + var expectation = result.failedExpectations[i]; + messages.appendChild(createDom("div", {className: "result-message"}, expectation.message)); + messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack)); + } + + failures.push(failure); + } + + if (result.status == "pending") { + pendingSpecCount++; + } + }; + + this.jasmineDone = function() { + var banner = find(".banner"); + banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s")); + + var alert = find(".alert"); + + alert.appendChild(createDom("span", { className: "exceptions" }, + createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"), + createDom("input", { + className: "raise", + id: "raise-exceptions", + type: "checkbox" + }) + )); + var checkbox = find("input"); + + checkbox.checked = !env.catchingExceptions(); + checkbox.onclick = onRaiseExceptionsClick; + + if (specsExecuted < totalSpecsDefined) { + var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all"; + alert.appendChild( + createDom("span", {className: "bar skipped"}, + createDom("a", {href: "?", title: "Run all specs"}, skippedMessage) + ) + ); + } + var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount); + if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); } + + var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed"); + alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage)); + + var results = find(".results"); + results.appendChild(summary); + + summaryList(topResults, summary); + + function summaryList(resultsTree, domParent) { + var specListNode; + for (var i = 0; i < resultsTree.children.length; i++) { + var resultNode = resultsTree.children[i]; + if (resultNode.type == "suite") { + var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id}, + createDom("li", {className: "suite-detail"}, + createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + + summaryList(resultNode, suiteListNode); + domParent.appendChild(suiteListNode); + } + if (resultNode.type == "spec") { + if (domParent.getAttribute("class") != "specs") { + specListNode = createDom("ul", {className: "specs"}); + domParent.appendChild(specListNode); + } + specListNode.appendChild( + createDom("li", { + className: resultNode.result.status, + id: "spec-" + resultNode.result.id + }, + createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + } + } + } + + if (failures.length) { + alert.appendChild( + createDom('span', {className: "menu bar spec-list"}, + createDom("span", {}, "Spec List | "), + createDom('a', {className: "failures-menu", href: "#"}, "Failures"))); + alert.appendChild( + createDom('span', {className: "menu bar failure-list"}, + createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"), + createDom("span", {}, " | Failures "))); + + find(".failures-menu").onclick = function() { + setMenuModeTo('failure-list'); + }; + find(".spec-list-menu").onclick = function() { + setMenuModeTo('spec-list'); + }; + + setMenuModeTo('failure-list'); + + var failureNode = find(".failures"); + for (var i = 0; i < failures.length; i++) { + failureNode.appendChild(failures[i]); + } + } + }; + + return this; + + function find(selector) { + return getContainer().querySelector(selector); + } + + function createDom(type, attrs, childrenVarArgs) { + var el = createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(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; + } + + function pluralize(singular, count) { + var word = (count == 1 ? singular : singular + "s"); + + return "" + count + " " + word; + } + + function specHref(result) { + return "?spec=" + encodeURIComponent(result.fullName); + } + + function setMenuModeTo(mode) { + htmlReporterMain.setAttribute("class", "html-reporter " + mode); + } + } + + return HtmlReporter; +}; + +jasmineRequire.HtmlSpecFilter = function() { + function HtmlSpecFilter(options) { + var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + var filterPattern = new RegExp(filterString); + + this.matches = function(specName) { + return filterPattern.test(specName); + }; + } + + return HtmlSpecFilter; +}; + +jasmineRequire.ResultsNode = function() { + function ResultsNode(result, type, parent) { + this.result = result; + this.type = type; + this.parent = parent; + + this.children = []; + + this.addChild = function(result, type) { + this.children.push(new ResultsNode(result, type, this)); + }; + + this.last = function() { + return this.children[this.children.length - 1]; + }; + } + + return ResultsNode; +}; + +jasmineRequire.QueryString = function() { + function QueryString(options) { + + this.setParam = function(key, value) { + var paramMap = queryStringToParamMap(); + paramMap[key] = value; + options.getWindowLocation().search = toQueryString(paramMap); + }; + + this.getParam = function(key) { + return queryStringToParamMap()[key]; + }; + + return this; + + function toQueryString(paramMap) { + var qStrPairs = []; + for (var prop in paramMap) { + qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop])); + } + return "?" + qStrPairs.join('&'); + } + + function queryStringToParamMap() { + var paramStr = options.getWindowLocation().search.substring(1), + params = [], + paramMap = {}; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + var value = decodeURIComponent(p[1]); + if (value === "true" || value === "false") { + value = JSON.parse(value); + } + paramMap[decodeURIComponent(p[0])] = value; + } + } + + return paramMap; + } + + } + + return QueryString; +}; diff --git a/test/lib/jasmine.css b/test/lib/jasmine.css new file mode 100644 index 0000000..f4d35b6 --- /dev/null +++ b/test/lib/jasmine.css @@ -0,0 +1,55 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +.html-reporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +.html-reporter a { text-decoration: none; } +.html-reporter a:hover { text-decoration: underline; } +.html-reporter p, .html-reporter h1, .html-reporter h2, .html-reporter h3, .html-reporter h4, .html-reporter h5, .html-reporter h6 { margin: 0; line-height: 14px; } +.html-reporter .banner, .html-reporter .symbol-summary, .html-reporter .summary, .html-reporter .result-message, .html-reporter .spec .description, .html-reporter .spec-detail .description, .html-reporter .alert .bar, .html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } +.html-reporter .banner .version { margin-left: 14px; } +.html-reporter #jasmine_content { position: fixed; right: 100%; } +.html-reporter .version { color: #aaaaaa; } +.html-reporter .banner { margin-top: 14px; } +.html-reporter .duration { color: #aaaaaa; float: right; } +.html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } +.html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } +.html-reporter .symbol-summary li.passed { font-size: 14px; } +.html-reporter .symbol-summary li.passed:before { color: #5e7d00; content: "\02022"; } +.html-reporter .symbol-summary li.failed { line-height: 9px; } +.html-reporter .symbol-summary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +.html-reporter .symbol-summary li.disabled { font-size: 14px; } +.html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } +.html-reporter .symbol-summary li.pending { line-height: 17px; } +.html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } +.html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +.html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +.html-reporter .bar.failed { background-color: #b03911; } +.html-reporter .bar.passed { background-color: #a6b779; } +.html-reporter .bar.skipped { background-color: #bababa; } +.html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } +.html-reporter .bar.menu a { color: #333333; } +.html-reporter .bar a { color: white; } +.html-reporter.spec-list .bar.menu.failure-list, .html-reporter.spec-list .results .failures { display: none; } +.html-reporter.failure-list .bar.menu.spec-list, .html-reporter.failure-list .summary { display: none; } +.html-reporter .running-alert { background-color: #666666; } +.html-reporter .results { margin-top: 14px; } +.html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +.html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +.html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +.html-reporter.showDetails .summary { display: none; } +.html-reporter.showDetails #details { display: block; } +.html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +.html-reporter .summary { margin-top: 14px; } +.html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } +.html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } +.html-reporter .summary li.passed a { color: #5e7d00; } +.html-reporter .summary li.failed a { color: #b03911; } +.html-reporter .summary li.pending a { color: #ba9d37; } +.html-reporter .description + .suite { margin-top: 0; } +.html-reporter .suite { margin-top: 14px; } +.html-reporter .suite a { color: #333333; } +.html-reporter .failures .spec-detail { margin-bottom: 28px; } +.html-reporter .failures .spec-detail .description { background-color: #b03911; } +.html-reporter .failures .spec-detail .description a { color: white; } +.html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; } +.html-reporter .result-message span.result { display: block; } +.html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } diff --git a/test/lib/jasmine.js b/test/lib/jasmine.js new file mode 100644 index 0000000..24463ec --- /dev/null +++ b/test/lib/jasmine.js @@ -0,0 +1,2402 @@ +/* +Copyright (c) 2008-2013 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. +*/ +function getJasmineRequireObj() { + if (typeof module !== "undefined" && module.exports) { + return exports; + } else { + window.jasmineRequire = window.jasmineRequire || {}; + return window.jasmineRequire; + } +} + +getJasmineRequireObj().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$); + j$.util = jRequire.util(); + j$.Any = jRequire.Any(); + j$.CallTracker = jRequire.CallTracker(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.Suite = jRequire.Suite(); + j$.Timer = jRequire.Timer(); + j$.version = jRequire.version(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; +}; + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + "toBe", + "toBeCloseTo", + "toBeDefined", + "toBeFalsy", + "toBeGreaterThan", + "toBeLessThan", + "toBeNaN", + "toBeNull", + "toBeTruthy", + "toBeUndefined", + "toContain", + "toEqual", + "toHaveBeenCalled", + "toHaveBeenCalledWith", + "toMatch", + "toThrow", + "toThrowError" + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$) { + j$.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = (function() { + var jasmineGlobal = eval.call(null, "this"); + return function() { + return jasmineGlobal; + }; + })(); + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_("Array", value); + }; + + j$.isString_ = function(value) { + return j$.isA_("String", value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_("Number", value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + callTracker.track({ + object: this, + args: Array.prototype.slice.apply(arguments) + }); + return spyStrategy.exec.apply(this, arguments); + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw "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]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.fn = attrs.fn; + this.beforeFns = attrs.beforeFns || function() { return []; }; + this.afterFns = attrs.afterFns || function() { return []; }; + this.onStart = attrs.onStart || function() {}; + this.exceptionFormatter = attrs.exceptionFormatter || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + + this.timer = attrs.timer || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + + if (!this.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Spec.prototype.addExpectationResult = function(passed, data) { + if (passed) { + return; + } + this.result.failedExpectations.push(this.expectationResultFactory(data)); + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete) { + var self = this, + timeout; + + this.onStart(this); + + if (this.markedPending || this.disabled) { + complete(); + return; + } + + function timeoutable(fn) { + return function(done) { + timeout = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { + onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); + done(); + }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + + var callDone = function() { + clearTimeoutable(); + done(); + }; + + fn.call(this, callDone); //TODO: do we care about more than 1 arg? + }; + } + + function clearTimeoutable() { + Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeout]]); + timeout = void 0; + } + + var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()), + allTimeoutableFns = []; + for (var i = 0; i < allFns.length; i++) { + var fn = allFns[i]; + allTimeoutableFns.push(fn.length > 0 ? timeoutable(fn) : fn); + } + + this.queueRunnerFactory({ + fns: allTimeoutableFns, + onException: onException, + onComplete: complete + }); + + function onException(e) { + clearTimeoutable(); + if (Spec.isPendingSpecException(e)) { + self.pend(); + return; + } + + self.addExpectationResult(false, { + matcherName: "", + passed: false, + expected: "", + actual: "", + error: e + }); + } + + function complete() { + self.result.status = self.status(); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function() { + this.markedPending = true; + }; + + Spec.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + Spec.pendingSpecExceptionMessage = "=> marked Pending"; + + Spec.isPendingSpecException = function(e) { + return e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1; + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == "object") { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); + + var runnableLookupTable = {}; + + var spies = []; + + var currentSpec = null; + var currentSuite = null; + + var reporter = new j$.ReportDispatcher([ + "jasmineStarted", + "jasmineDone", + "suiteStarted", + "suiteDone", + "specStarted", + "specDone" + ]); + + this.specFilter = function() { + return true; + }; + + var equalityTesters = []; + + var customEqualityTesters = []; + this.addCustomEqualityTester = function(tester) { + customEqualityTesters.push(tester); + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: customEqualityTesters, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var specStarted = function(spec) { + currentSpec = spec; + reporter.specStarted(spec.result); + }; + + var beforeFns = function(suite) { + return function() { + var befores = []; + while(suite) { + befores = befores.concat(suite.beforeFns); + suite = suite.parentSuite; + } + return befores.reverse(); + }; + }; + + var afterFns = function(suite) { + return function() { + var afters = []; + while(suite) { + afters = afters.concat(suite.afterFns); + suite = suite.parentSuite; + } + return afters; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory, + resultCallback: function() {} // TODO - hook this up + }); + runnableLookupTable[topSuite.id] = topSuite; + currentSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + runnablesToRun = runnablesToRun || [topSuite.id]; + + var allFns = []; + for(var i = 0; i < runnablesToRun.length; i++) { + var runnable = runnableLookupTable[runnablesToRun[i]]; + allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + this.addMatchers = function(matchersToAdd) { + j$.Expectation.addMatchers(matchersToAdd); + }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error("spyOn could not find an object to spy upon for " + methodName + "()"); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + spies.push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentSuite, + queueRunner: queueRunnerFactory, + onStart: suiteStarted, + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + + var parentSuite = currentSuite; + parentSuite.addChild(suite); + currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + currentSuite = parentSuite; + + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var specFactory = function(description, fn, suite) { + totalSpecsDefined++; + + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeFns: beforeFns(suite), + afterFns: afterFns(suite), + expectationFactory: expectationFactory, + exceptionFormatter: exceptionFormatter, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + fn: fn, + timer: {setTimeout: realSetTimeout, clearTimeout: realClearTimeout} + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function removeAllSpies() { + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + spies = []; + } + + function specResultCallback(result) { + removeAllSpies(); + j$.Expectation.resetMatchers(); + customEqualityTesters = []; + currentSpec = null; + reporter.specDone(result); + } + }; + + var suiteStarted = function(suite) { + reporter.suiteStarted(suite.result); + }; + + this.it = function(description, fn) { + var spec = specFactory(description, fn, currentSuite); + currentSuite.addChild(spec); + return spec; + }; + + this.xit = function(description, fn) { + var spec = this.it(description, fn); + spec.pend(); + return spec; + }; + + this.expect = function(actual) { + return currentSpec.expect(actual); + }; + + this.beforeEach = function(beforeEachFunction) { + currentSuite.beforeEach(beforeEachFunction); + }; + + this.afterEach = function(afterEachFunction) { + currentSuite.afterEach(afterEachFunction); + }; + + this.pending = function() { + throw j$.Spec.pendingSpecExceptionMessage; + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = "loaded"; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = {}; + + this.suiteStarted = function(result) { + storeSuite(result); + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + function storeSuite(result) { + suites[result.id] = result; + } + + this.suites = function() { + return suites; + }; + + var specs = []; + this.specStarted = function(result) { }; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().Any = function() { + + function Any(expectedObject) { + this.expectedObject = expectedObject; + } + + Any.prototype.jasmineMatches = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionScheduler) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + timer; + + self.install = function() { + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + installed = true; + }; + + self.uninstall = function() { + delayedFunctionScheduler.reset(); + replace(global, realTimingFunctions); + timer = realTimingFunctions; + installed = false; + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill"); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill"); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + delayedFunctionScheduler.tick(millis); + } else { + throw new Error("Mock clock is not installed, use jasmine.clock().install()"); + } + }; + + return self; + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, 2); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + self.reset = function() { + currentTime = 0; + scheduledLookup = []; + scheduledFunctions = {}; + delayedFnCount = 0; + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + for (var i = 0; i < funcsToRun.length; ++i) { + var funcToRun = funcsToRun[i]; + funcToRun.funcToCall.apply(null, funcToRun.params || []); + + if (funcToRun.recurring) { + reschedule(funcToRun); + } + } + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = error.name + + ': ' + + error.message; + + if (error.fileName || error.sourceURL) { + message += " in " + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += " (line " + (error.line || error.lineNumber) + ")"; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + var matchers = {}; + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + for (var matcherName in matchers) { + this[matcherName] = matchers[matcherName]; + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ""; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + message = result.message; + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.addMatchers = function(matchersToAdd) { + for (var name in matchersToAdd) { + var matcher = matchersToAdd[name]; + matchers[name] = Expectation.prototype.wrapCompare(name, matcher); + } + }; + + Expectation.resetMatchers = function() { + for (var name in matchers) { + delete matchers[name]; + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + return { + matcherName: options.matcherName, + expected: options.expected, + actual: options.actual, + message: message(), + stack: stack(), + passed: options.passed + }; + + function message() { + if (options.passed) { + return "Passed."; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ""; + } + + function stack() { + if (options.passed) { + return ""; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().ObjectContaining = function(j$) { + + function ObjectContaining(sample) { + this.sample = sample; + } + + ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + if (typeof(this.sample) !== "object") { throw new Error("You must provide an object to objectContaining, not '"+this.sample+"'."); } + + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var hasKey = function(obj, keyName) { + return obj !== null && !j$.util.isUndefined(obj[keyName]); + }; + + 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 (!j$.matchersUtil.equals(this.sample[property], other[property])) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in actual, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in expected."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); + }; + + ObjectContaining.prototype.jasmineToString = function() { + return ""; + }; + + return ObjectContaining; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === j$.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar("spy on " + value.and.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 (j$.isArray_(value) || j$.isA_('Object', value)) { + value.__Jasmine_been_here_before__ = true; + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + 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__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.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(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > j$.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(' }'); + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function() { + + function QueueRunner(attrs) { + this.fns = attrs.fns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.fns, 0); + }; + + QueueRunner.prototype.run = function(fns, recursiveIndex) { + var length = fns.length, + self = this, + iterativeIndex; + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var fn = fns[iterativeIndex]; + if (fn.length > 0) { + return attemptAsync(fn); + } else { + attemptSync(fn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(fn) { + try { + fn.call(self.userContext); + } catch (e) { + handleException(e); + } + } + + function attemptAsync(fn) { + var next = function () { self.run(fns, iterativeIndex + 1); }; + + try { + fn.call(self.userContext, next); + } catch (e) { + handleException(e); + next(); + } + } + + function handleException(e) { + self.onException(e); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || "unknown", + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function() { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.onStart = attrs.onStart || function() {}; + this.resultCallback = attrs.resultCallback || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + + this.beforeFns = []; + this.afterFns = []; + this.queueRunner = attrs.queueRunner || function() {}; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + status: this.disabled ? 'disabled' : '', + description: this.description, + fullName: this.getFullName() + }; + } + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.execute = function(onComplete) { + var self = this; + if (this.disabled) { + complete(); + return; + } + + var allFns = []; + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + this.onStart(this); + + this.queueRunner({ + fns: allFns, + onComplete: complete + }); + + function complete() { + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + + function wrapChildAsAsync(child) { + return function(done) { child.execute(done); }; + } + }; + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == "object") { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + function Timer(options) { + options = options || {}; + + var now = options.now || function() { return new Date().getTime(); }, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if (Object.prototype.toString.apply(haystack) === "[object Array]") { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + return haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = "Expected " + + j$.pp(actual) + + (isNot ? " not " : " ") + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ","; + } + message += " " + j$.pp(expected[i]); + } + } + + return message + "."; + } + }; + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof j$.Any) { + result = a.jasmineMatches(b); + if (result) { + return true; + } + } + + if (b instanceof j$.Any) { + result = b.jasmineMatches(a); + if (result) { + return true; + } + } + + if (b instanceof j$.ObjectContaining) { + result = b.jasmineMatches(a); + if (result) { + return true; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && + isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return obj.hasOwnProperty(key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } + + return toBe; +}; + +getJasmineRequireObj().toBeCloseTo = function() { + + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } + + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } + + return toBeFalsy; +}; + +getJasmineRequireObj().toBeGreaterThan = function() { + + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } + + return toBeGreaterThan; +}; + + +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { + + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; + + if (result.pass) { + result.message = "Expected actual not to be NaN."; + } else { + result.message = "Expected " + j$.pp(actual) + " to be NaN."; + } + + return result; + } + }; + } + + return toBeNaN; +}; + +getJasmineRequireObj().toBeNull = function() { + + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } + + return toBeNull; +}; + +getJasmineRequireObj().toBeTruthy = function() { + + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + + return toBeTruthy; +}; + +getJasmineRequireObj().toBeUndefined = function() { + + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } + + return toBeUndefined; +}; + +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } + + return toContain; +}; + +getJasmineRequireObj().toEqual = function() { + + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + result.pass = util.equals(actual, expected, customEqualityTesters); + + return result; + } + }; + } + + return toEqual; +}; + +getJasmineRequireObj().toHaveBeenCalled = function(j$) { + + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + result.pass = actual.calls.any(); + + result.message = result.pass ? + "Expected spy " + actual.and.identity() + " not to have been called." : + "Expected spy " + actual.and.identity() + " to have been called."; + + return result; + } + }; + } + + return toHaveBeenCalled; +}; + +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { + + function toHaveBeenCalledWith(util) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (!actual.calls.any()) { + result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but it was never called."; + return result; + } + + if (util.contains(actual.calls.allArgs(), expectedArgs)) { + result.pass = true; + result.message = "Expected spy " + actual.and.identity() + " not to have been called with " + j$.pp(expectedArgs) + " but it was."; + } else { + result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + "."; + } + + return result; + } + }; + } + + return toHaveBeenCalledWith; +}; + +getJasmineRequireObj().toMatch = function() { + + function toMatch() { + return { + compare: function(actual, expected) { + var regexp = new RegExp(expected); + + return { + pass: regexp.test(actual) + }; + } + }; + } + + return toMatch; +}; + +getJasmineRequireObj().toThrow = function(j$) { + + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; + + if (typeof actual != "function") { + throw new Error("Actual is not a Function"); + } + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + result.message = "Expected function to throw an exception."; + return result; + } + + if (arguments.length == 1) { + result.pass = true; + result.message = "Expected function not to throw, but it threw " + j$.pp(thrown) + "."; + + return result; + } + + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = "Expected function not to throw " + j$.pp(expected) + "."; + } else { + result.message = "Expected function to throw " + j$.pp(expected) + ", but it threw " + j$.pp(thrown) + "."; + } + + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + thrown, + errorType, + message, + regexp, + name, + constructorName; + + if (typeof actual != "function") { + throw new Error("Actual is not a Function"); + } + + extractExpectedParams.apply(null, arguments); + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + return fail("Expected function to throw an Error."); + } + + if (!(thrown instanceof Error)) { + return fail("Expected function to throw an Error, but it threw " + thrown + "."); + } + + if (arguments.length == 1) { + return pass("Expected function not to throw an Error, but it threw " + fnNameFor(thrown) + "."); + } + + if (errorType) { + name = fnNameFor(errorType); + constructorName = fnNameFor(thrown.constructor); + } + + if (errorType && message) { + if (thrown.constructor == errorType && util.equals(thrown.message, message)) { + return pass("Expected function not to throw " + name + " with message \"" + message + "\"."); + } else { + return fail("Expected function to throw " + name + " with message \"" + message + + "\", but it threw " + constructorName + " with message \"" + thrown.message + "\"."); + } + } + + if (errorType && regexp) { + if (thrown.constructor == errorType && regexp.test(thrown.message)) { + return pass("Expected function not to throw " + name + " with message matching " + regexp + "."); + } else { + return fail("Expected function to throw " + name + " with message matching " + regexp + + ", but it threw " + constructorName + " with message \"" + thrown.message + "\"."); + } + } + + if (errorType) { + if (thrown.constructor == errorType) { + return pass("Expected function not to throw " + name + "."); + } else { + return fail("Expected function to throw " + name + ", but it threw " + constructorName + "."); + } + } + + if (message) { + if (thrown.message == message) { + return pass("Expected function not to throw an exception with message " + j$.pp(message) + "."); + } else { + return fail("Expected function to throw an exception with message " + j$.pp(message) + + ", but it threw an exception with message " + j$.pp(thrown.message) + "."); + } + } + + if (regexp) { + if (regexp.test(thrown.message)) { + return pass("Expected function not to throw an exception with a message matching " + j$.pp(regexp) + "."); + } else { + return fail("Expected function to throw an exception with a message matching " + j$.pp(regexp) + + ", but it threw an exception with message " + j$.pp(thrown.message) + "."); + } + } + + function fnNameFor(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + } + + function pass(notMessage) { + return { + pass: true, + message: notMessage + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + + function extractExpectedParams() { + if (arguments.length == 1) { + return; + } + + if (arguments.length == 2) { + var expected = arguments[1]; + + if (expected instanceof RegExp) { + regexp = expected; + } else if (typeof expected == "string") { + message = expected; + } else if (checkForAnErrorType(expected)) { + errorType = expected; + } + + if (!(errorType || message || regexp)) { + throw new Error("Expected is not an Error, string, or RegExp."); + } + } else { + if (checkForAnErrorType(arguments[1])) { + errorType = arguments[1]; + } else { + throw new Error("Expected error type is not an Error."); + } + + if (arguments[2] instanceof RegExp) { + regexp = arguments[2]; + } else if (typeof arguments[2] == "string") { + message = arguments[2]; + } else { + throw new Error("Expected error message is not a string or RegExp."); + } + } + } + + function checkForAnErrorType(type) { + if (typeof type !== "function") { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + }; + } + + return toThrowError; +}; + +getJasmineRequireObj().version = function() { + return "2.0.0"; +}; diff --git a/test/lib/jasmine_favicon.png b/test/lib/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3562e278f108d0f6a918d198f21e055e601c7e71 GIT binary patch literal 2057 zcmV+k2=@1hP)&ijKvfTC%$Wid1aFR2$ii+A1Wx?rx%@ z&1NNpLfWZ1g6QrB21_kem?Ba;0|mzpS}7H&g-YZnkRK=6?7O$$ecAi=Wg&q(^Y)(K zd(J)Q+;i`el#*S{2tOPiRoYFrGh&sD(R7k*sw7qlU9~_bzQZKtej(Y8DaO^i{0YBx zG3!ESWCvpN8mF*~^%OCB2n&wAm_;G;1?1QEc|9wiUjMXmZrCxb1K~i}TPpZ6?D&_^ zE*y4+kr}5YqouXUy0ycJo!5b;s>Z1X7ww46j66k4lKd-4+6~@8D0jj^j&C9fnkG5j z%A~vwI+Ulk#ZxFAs99%r<Qy@CSV`29g zvVeMmZ`A$MrNf$h;R;UlDokAO(20Fm$i&T$Cd?j9vlmcE%j}jy7~crcF^)p|RVZ11 zU%qiA&n4R`INPH>W{EaMbB`xd<|kbwXQBqyJ9NpW_cQ*Ag9C6Ju!=fO;3|KEgP0ji z)`b-kt7^P4?vFg1%z`J*qYRG0}f-F>a~5NhU4M@h1(m@oRE#%%}UzHm7_eL@p4 zZwe*qBN{K1{1EsHVu6NxHEp=LIS_dSJ>srWY3rDhF-=pPEwO%#viwl%aAGUk?hTJo z_+SKxegW8<6*%J|&*Cg>PmgVU<`foq5CzlLQWY+9^W3n?JCpPRXJT&kTjp1qXTEIq zNx>clZZ{`Y;5}Zhij?uyfp9T)_kb2g-eWb1XHLK%g~MSdMTp3|lF7CV=dUFLNkhx) zS=A8d{0-=tOH$@PG+oQqrs_YyNpey_#OzIa(#*IMbzGcPNE>9VQE-z#`mNFfd~?hn z*+@j~WvpU%_emKkoycA|%nldpaA`y7(Or|dkUK-E<*RaSn^$4&JL5kJR|GeiXC(1f zVq$()XIir_dJRV_uGEXb`(5_#LEwVO2Kt9-`ZQ;h7G zqk-%^2^!T{blcbo{I}>0KZKLlGvYT4q`gaeN6`*!CJEWO_)6Tmg3;hw$f4?@nul7r z&I9D@If6W>V7o5J*hfrG2C8nnAp?ru;9y5{x*B#~I7BFibdZq$(I|YdZP+-MQ}ywYVUCG z0U>cK(4ZzDscPViUI@FKs9`m?1fIJj=8OCk;r&y;LG9#m+3t$Q(hz9z*9`)9i1!qiu0pftFJEZ@$WPPVR=m5jv9Z-7rg3{7vVKRiDi3GKlM2QBvw-E zOY@(-RMXJXZv%|BpB<8|RuF1{;n6E&n7S2=F9u=%CrdhuGI~TmWN;UZLkDy=37JDh6DGf|WNinY+-QD@5Sv+L9CiBM zVBZG?D+QDFzeZW!1Z;CxiFiuOxo36nGhr+QZHo|mdTnyK-K^BbBt1NZC|=>n6FpwN zw%Sga^CnbRZ|O&T8w}3Uf|9Y{ca!*;df8~2d&W6`1qJ+mFj3#;rfXR3+&0db3co$D zpH?{cbB)^(P>9-rH{+QL2mYm*2A4|1)Y1G+xf6*p7uN^kSGTD6f~2w}ev5{_dg=0aX->Kcd_4M$=;9?x? zYV20E-3tyly%$2OyHAL9=3mUrND3Epz^&XIh<*dF+e%ze-^BTxirwLSODTw*L>`Xd nulYW~`SvFhb)UKQMuPtji`gObi$ZJx00000NkvXXu0mjf%0~0J literal 0 HcmV?d00001 diff --git a/test/lib/sinon.js b/test/lib/sinon.js new file mode 100644 index 0000000..3f31fb0 --- /dev/null +++ b/test/lib/sinon.js @@ -0,0 +1,4299 @@ +/** + * Sinon.JS 1.7.1, 2013/05/07 + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS + * + * (The BSD License) + * + * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Christian Johansen nor the names of his contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +this.sinon = (function () { + var buster = (function (setTimeout, B) { + var isNode = typeof require == "function" && typeof module == "object"; + var div = typeof document != "undefined" && document.createElement("div"); + var F = function () {}; + + var buster = { + bind: function bind(obj, methOrProp) { + var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp; + var args = Array.prototype.slice.call(arguments, 2); + return function () { + var allArgs = args.concat(Array.prototype.slice.call(arguments)); + return method.apply(obj, allArgs); + }; + }, + + partial: function partial(fn) { + var args = [].slice.call(arguments, 1); + return function () { + return fn.apply(this, args.concat([].slice.call(arguments))); + }; + }, + + create: function create(object) { + F.prototype = object; + return new F(); + }, + + extend: function extend(target) { + if (!target) { return; } + for (var i = 1, l = arguments.length, prop; i < l; ++i) { + for (prop in arguments[i]) { + target[prop] = arguments[i][prop]; + } + } + return target; + }, + + nextTick: function nextTick(callback) { + if (typeof process != "undefined" && process.nextTick) { + return process.nextTick(callback); + } + setTimeout(callback, 0); + }, + + functionName: function functionName(func) { + if (!func) return ""; + if (func.displayName) return func.displayName; + if (func.name) return func.name; + var matches = func.toString().match(/function\s+([^\(]+)/m); + return matches && matches[1] || ""; + }, + + isNode: function isNode(obj) { + if (!div) return false; + try { + obj.appendChild(div); + obj.removeChild(div); + } catch (e) { + return false; + } + return true; + }, + + isElement: function isElement(obj) { + return obj && obj.nodeType === 1 && buster.isNode(obj); + }, + + isArray: function isArray(arr) { + return Object.prototype.toString.call(arr) == "[object Array]"; + }, + + flatten: function flatten(arr) { + var result = [], arr = arr || []; + for (var i = 0, l = arr.length; i < l; ++i) { + result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]); + } + return result; + }, + + each: function each(arr, callback) { + for (var i = 0, l = arr.length; i < l; ++i) { + callback(arr[i]); + } + }, + + map: function map(arr, callback) { + var results = []; + for (var i = 0, l = arr.length; i < l; ++i) { + results.push(callback(arr[i])); + } + return results; + }, + + parallel: function parallel(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + callback = null; + } + } + if (fns.length == 0) { return cb(null, []); } + var remaining = fns.length, results = []; + function makeDone(num) { + return function done(err, result) { + if (err) { return cb(err); } + results[num] = result; + if (--remaining == 0) { cb(null, results); } + }; + } + for (var i = 0, l = fns.length; i < l; ++i) { + fns[i](makeDone(i)); + } + }, + + series: function series(fns, callback) { + function cb(err, res) { + if (typeof callback == "function") { + callback(err, res); + } + } + var remaining = fns.slice(); + var results = []; + function callNext() { + if (remaining.length == 0) return cb(null, results); + var promise = remaining.shift()(next); + if (promise && typeof promise.then == "function") { + promise.then(buster.partial(next, null), next); + } + } + function next(err, result) { + if (err) return cb(err); + results.push(result); + callNext(); + } + callNext(); + }, + + countdown: function countdown(num, done) { + return function () { + if (--num == 0) done(); + }; + } + }; + + if (typeof process === "object" && + typeof require === "function" && typeof module === "object") { + var crypto = require("crypto"); + var path = require("path"); + + buster.tmpFile = function (fileName) { + var hashed = crypto.createHash("sha1"); + hashed.update(fileName); + var tmpfileName = hashed.digest("hex"); + + if (process.platform == "win32") { + return path.join(process.env["TEMP"], tmpfileName); + } else { + return path.join("/tmp", tmpfileName); + } + }; + } + + if (Array.prototype.some) { + buster.some = function (arr, fn, thisp) { + return arr.some(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some + buster.some = function (arr, fun, thisp) { + if (arr == null) { throw new TypeError(); } + arr = Object(arr); + var len = arr.length >>> 0; + if (typeof fun !== "function") { throw new TypeError(); } + + for (var i = 0; i < len; i++) { + if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) { + return true; + } + } + + return false; + }; + } + + if (Array.prototype.filter) { + buster.filter = function (arr, fn, thisp) { + return arr.filter(fn, thisp); + }; + } else { + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter + buster.filter = function (fn, thisp) { + if (this == null) { throw new TypeError(); } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fn != "function") { throw new TypeError(); } + + var res = []; + for (var i = 0; i < len; i++) { + if (i in t) { + var val = t[i]; // in case fun mutates this + if (fn.call(thisp, val, i, t)) { res.push(val); } + } + } + + return res; + }; + } + + if (isNode) { + module.exports = buster; + buster.eventEmitter = require("./buster-event-emitter"); + Object.defineProperty(buster, "defineVersionGetter", { + get: function () { + return require("./define-version-getter"); + } + }); + } + + return buster.extend(B || {}, buster); + }(setTimeout, buster)); + if (typeof buster === "undefined") { + var buster = {}; + } + + if (typeof module === "object" && typeof require === "function") { + buster = require("buster-core"); + } + + buster.format = buster.format || {}; + buster.format.excludeConstructors = ["Object", /^.$/]; + buster.format.quoteStrings = true; + + buster.format.ascii = (function () { + + var hasOwn = Object.prototype.hasOwnProperty; + + var specialObjects = []; + if (typeof global != "undefined") { + specialObjects.push({ obj: global, value: "[object global]" }); + } + if (typeof document != "undefined") { + specialObjects.push({ obj: document, value: "[object HTMLDocument]" }); + } + if (typeof window != "undefined") { + specialObjects.push({ obj: window, value: "[object Window]" }); + } + + function keys(object) { + var k = Object.keys && Object.keys(object) || []; + + if (k.length == 0) { + for (var prop in object) { + if (hasOwn.call(object, prop)) { + k.push(prop); + } + } + } + + return k.sort(); + } + + function isCircular(object, objects) { + if (typeof object != "object") { + return false; + } + + for (var i = 0, l = objects.length; i < l; ++i) { + if (objects[i] === object) { + return true; + } + } + + return false; + } + + function ascii(object, processed, indent) { + if (typeof object == "string") { + var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings; + return processed || quote ? '"' + object + '"' : object; + } + + if (typeof object == "function" && !(object instanceof RegExp)) { + return ascii.func(object); + } + + processed = processed || []; + + if (isCircular(object, processed)) { + return "[Circular]"; + } + + if (Object.prototype.toString.call(object) == "[object Array]") { + return ascii.array.call(this, object, processed); + } + + if (!object) { + return "" + object; + } + + if (buster.isElement(object)) { + return ascii.element(object); + } + + if (typeof object.toString == "function" && + object.toString !== Object.prototype.toString) { + return object.toString(); + } + + for (var i = 0, l = specialObjects.length; i < l; i++) { + if (object === specialObjects[i].obj) { + return specialObjects[i].value; + } + } + + return ascii.object.call(this, object, processed, indent); + } + + ascii.func = function (func) { + return "function " + buster.functionName(func) + "() {}"; + }; + + ascii.array = function (array, processed) { + processed = processed || []; + processed.push(array); + var pieces = []; + + for (var i = 0, l = array.length; i < l; ++i) { + pieces.push(ascii.call(this, array[i], processed)); + } + + return "[" + pieces.join(", ") + "]"; + }; + + ascii.object = function (object, processed, indent) { + processed = processed || []; + processed.push(object); + indent = indent || 0; + var pieces = [], properties = keys(object), prop, str, obj; + var is = ""; + var length = 3; + + for (var i = 0, l = indent; i < l; ++i) { + is += " "; + } + + for (i = 0, l = properties.length; i < l; ++i) { + prop = properties[i]; + obj = object[prop]; + + if (isCircular(obj, processed)) { + str = "[Circular]"; + } else { + str = ascii.call(this, obj, processed, indent + 2); + } + + str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str; + length += str.length; + pieces.push(str); + } + + var cons = ascii.constructorName.call(this, object); + var prefix = cons ? "[" + cons + "] " : "" + + return (length + indent) > 80 ? + prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" : + prefix + "{ " + pieces.join(", ") + " }"; + }; + + ascii.element = function (element) { + var tagName = element.tagName.toLowerCase(); + var attrs = element.attributes, attribute, pairs = [], attrName; + + for (var i = 0, l = attrs.length; i < l; ++i) { + attribute = attrs.item(i); + attrName = attribute.nodeName.toLowerCase().replace("html:", ""); + + if (attrName == "contenteditable" && attribute.nodeValue == "inherit") { + continue; + } + + if (!!attribute.nodeValue) { + pairs.push(attrName + "=\"" + attribute.nodeValue + "\""); + } + } + + var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); + var content = element.innerHTML; + + if (content.length > 20) { + content = content.substr(0, 20) + "[...]"; + } + + var res = formatted + pairs.join(" ") + ">" + content + ""; + + return res.replace(/ contentEditable="inherit"/, ""); + }; + + ascii.constructorName = function (object) { + var name = buster.functionName(object && object.constructor); + var excludes = this.excludeConstructors || buster.format.excludeConstructors || []; + + for (var i = 0, l = excludes.length; i < l; ++i) { + if (typeof excludes[i] == "string" && excludes[i] == name) { + return ""; + } else if (excludes[i].test && excludes[i].test(name)) { + return ""; + } + } + + return name; + }; + + return ascii; + }()); + + if (typeof module != "undefined") { + module.exports = buster.format; + } + /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/ + /*global module, require, __dirname, document*/ + /** + * Sinon core utilities. For internal use only. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + var sinon = (function (buster) { + var div = typeof document != "undefined" && document.createElement("div"); + var hasOwn = Object.prototype.hasOwnProperty; + + function isDOMNode(obj) { + var success = false; + + try { + obj.appendChild(div); + success = div.parentNode == obj; + } catch (e) { + return false; + } finally { + try { + obj.removeChild(div); + } catch (e) { + // Remove failed, not much we can do about that + } + } + + return success; + } + + function isElement(obj) { + return div && obj && obj.nodeType === 1 && isDOMNode(obj); + } + + function isFunction(obj) { + return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply); + } + + function mirrorProperties(target, source) { + for (var prop in source) { + if (!hasOwn.call(target, prop)) { + target[prop] = source[prop]; + } + } + } + + function isRestorable (obj) { + return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon; + } + + var sinon = { + wrapMethod: function wrapMethod(object, property, method) { + if (!object) { + throw new TypeError("Should wrap property of object"); + } + + if (typeof method != "function") { + throw new TypeError("Method wrapper should be function"); + } + + var wrappedMethod = object[property]; + + if (!isFunction(wrappedMethod)) { + throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " + + property + " as function"); + } + + if (wrappedMethod.restore && wrappedMethod.restore.sinon) { + throw new TypeError("Attempted to wrap " + property + " which is already wrapped"); + } + + if (wrappedMethod.calledBefore) { + var verb = !!wrappedMethod.returns ? "stubbed" : "spied on"; + throw new TypeError("Attempted to wrap " + property + " which is already " + verb); + } + + // IE 8 does not support hasOwnProperty on the window object. + var owned = hasOwn.call(object, property); + object[property] = method; + method.displayName = property; + + method.restore = function () { + // For prototype properties try to reset by delete first. + // If this fails (ex: localStorage on mobile safari) then force a reset + // via direct assignment. + if (!owned) { + delete object[property]; + } + if (object[property] === method) { + object[property] = wrappedMethod; + } + }; + + method.restore.sinon = true; + mirrorProperties(method, wrappedMethod); + + return method; + }, + + extend: function extend(target) { + for (var i = 1, l = arguments.length; i < l; i += 1) { + for (var prop in arguments[i]) { + if (arguments[i].hasOwnProperty(prop)) { + target[prop] = arguments[i][prop]; + } + + // DONT ENUM bug, only care about toString + if (arguments[i].hasOwnProperty("toString") && + arguments[i].toString != target.toString) { + target.toString = arguments[i].toString; + } + } + } + + return target; + }, + + create: function create(proto) { + var F = function () {}; + F.prototype = proto; + return new F(); + }, + + deepEqual: function deepEqual(a, b) { + if (sinon.match && sinon.match.isMatcher(a)) { + return a.test(b); + } + if (typeof a != "object" || typeof b != "object") { + return a === b; + } + + if (isElement(a) || isElement(b)) { + return a === b; + } + + if (a === b) { + return true; + } + + if ((a === null && b !== null) || (a !== null && b === null)) { + return false; + } + + var aString = Object.prototype.toString.call(a); + if (aString != Object.prototype.toString.call(b)) { + return false; + } + + if (aString == "[object Array]") { + if (a.length !== b.length) { + return false; + } + + for (var i = 0, l = a.length; i < l; i += 1) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + + return true; + } + + var prop, aLength = 0, bLength = 0; + + for (prop in a) { + aLength += 1; + + if (!deepEqual(a[prop], b[prop])) { + return false; + } + } + + for (prop in b) { + bLength += 1; + } + + return aLength == bLength; + }, + + functionName: function functionName(func) { + var name = func.displayName || func.name; + + // Use function decomposition as a last resort to get function + // name. Does not rely on function decomposition to work - if it + // doesn't debugging will be slightly less informative + // (i.e. toString will say 'spy' rather than 'myFunc'). + if (!name) { + var matches = func.toString().match(/function ([^\s\(]+)/); + name = matches && matches[1]; + } + + return name; + }, + + functionToString: function toString() { + if (this.getCall && this.callCount) { + var thisValue, prop, i = this.callCount; + + while (i--) { + thisValue = this.getCall(i).thisValue; + + for (prop in thisValue) { + if (thisValue[prop] === this) { + return prop; + } + } + } + } + + return this.displayName || "sinon fake"; + }, + + getConfig: function (custom) { + var config = {}; + custom = custom || {}; + var defaults = sinon.defaultConfig; + + for (var prop in defaults) { + if (defaults.hasOwnProperty(prop)) { + config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop]; + } + } + + return config; + }, + + format: function (val) { + return "" + val; + }, + + defaultConfig: { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }, + + timesInWords: function timesInWords(count) { + return count == 1 && "once" || + count == 2 && "twice" || + count == 3 && "thrice" || + (count || 0) + " times"; + }, + + calledInOrder: function (spies) { + for (var i = 1, l = spies.length; i < l; i++) { + if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) { + return false; + } + } + + return true; + }, + + orderByFirstCall: function (spies) { + return spies.sort(function (a, b) { + // uuid, won't ever be equal + var aCall = a.getCall(0); + var bCall = b.getCall(0); + var aId = aCall && aCall.callId || -1; + var bId = bCall && bCall.callId || -1; + + return aId < bId ? -1 : 1; + }); + }, + + log: function () {}, + + logError: function (label, err) { + var msg = label + " threw exception: " + sinon.log(msg + "[" + err.name + "] " + err.message); + if (err.stack) { sinon.log(err.stack); } + + setTimeout(function () { + err.message = msg + err.message; + throw err; + }, 0); + }, + + typeOf: function (value) { + if (value === null) { + return "null"; + } + else if (value === undefined) { + return "undefined"; + } + var string = Object.prototype.toString.call(value); + return string.substring(8, string.length - 1).toLowerCase(); + }, + + createStubInstance: function (constructor) { + if (typeof constructor !== "function") { + throw new TypeError("The constructor should be a function."); + } + return sinon.stub(sinon.create(constructor.prototype)); + }, + + restore: function (object) { + if (object !== null && typeof object === "object") { + for (var prop in object) { + if (isRestorable(object[prop])) { + object[prop].restore(); + } + } + } + else if (isRestorable(object)) { + object.restore(); + } + } + }; + + var isNode = typeof module == "object" && typeof require == "function"; + + if (isNode) { + try { + buster = { format: require("buster-format") }; + } catch (e) {} + module.exports = sinon; + module.exports.spy = require("./sinon/spy"); + module.exports.spyCall = require("./sinon/call"); + module.exports.stub = require("./sinon/stub"); + module.exports.mock = require("./sinon/mock"); + module.exports.collection = require("./sinon/collection"); + module.exports.assert = require("./sinon/assert"); + module.exports.sandbox = require("./sinon/sandbox"); + module.exports.test = require("./sinon/test"); + module.exports.testCase = require("./sinon/test_case"); + module.exports.assert = require("./sinon/assert"); + module.exports.match = require("./sinon/match"); + } + + if (buster) { + var formatter = sinon.create(buster.format); + formatter.quoteStrings = false; + sinon.format = function () { + return formatter.ascii.apply(formatter, arguments); + }; + } else if (isNode) { + try { + var util = require("util"); + sinon.format = function (value) { + return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value; + }; + } catch (e) { + /* Node, but no util module - would be very old, but better safe than + sorry */ + } + } + + return sinon; + }(typeof buster == "object" && buster)); + + /* @depend ../sinon.js */ + /*jslint eqeqeq: false, onevar: false, plusplus: false*/ + /*global module, require, sinon*/ + /** + * Match functions + * + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2012 Maximilian Antoni + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function assertType(value, type, name) { + var actual = sinon.typeOf(value); + if (actual !== type) { + throw new TypeError("Expected type of " + name + " to be " + + type + ", but was " + actual); + } + } + + var matcher = { + toString: function () { + return this.message; + } + }; + + function isMatcher(object) { + return matcher.isPrototypeOf(object); + } + + function matchObject(expectation, actual) { + if (actual === null || actual === undefined) { + return false; + } + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + var exp = expectation[key]; + var act = actual[key]; + if (match.isMatcher(exp)) { + if (!exp.test(act)) { + return false; + } + } else if (sinon.typeOf(exp) === "object") { + if (!matchObject(exp, act)) { + return false; + } + } else if (!sinon.deepEqual(exp, act)) { + return false; + } + } + } + return true; + } + + matcher.or = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var or = sinon.create(matcher); + or.test = function (actual) { + return m1.test(actual) || m2.test(actual); + }; + or.message = m1.message + ".or(" + m2.message + ")"; + return or; + }; + + matcher.and = function (m2) { + if (!isMatcher(m2)) { + throw new TypeError("Matcher expected"); + } + var m1 = this; + var and = sinon.create(matcher); + and.test = function (actual) { + return m1.test(actual) && m2.test(actual); + }; + and.message = m1.message + ".and(" + m2.message + ")"; + return and; + }; + + var match = function (expectation, message) { + var m = sinon.create(matcher); + var type = sinon.typeOf(expectation); + switch (type) { + case "object": + if (typeof expectation.test === "function") { + m.test = function (actual) { + return expectation.test(actual) === true; + }; + m.message = "match(" + sinon.functionName(expectation.test) + ")"; + return m; + } + var str = []; + for (var key in expectation) { + if (expectation.hasOwnProperty(key)) { + str.push(key + ": " + expectation[key]); + } + } + m.test = function (actual) { + return matchObject(expectation, actual); + }; + m.message = "match(" + str.join(", ") + ")"; + break; + case "number": + m.test = function (actual) { + return expectation == actual; + }; + break; + case "string": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return actual.indexOf(expectation) !== -1; + }; + m.message = "match(\"" + expectation + "\")"; + break; + case "regexp": + m.test = function (actual) { + if (typeof actual !== "string") { + return false; + } + return expectation.test(actual); + }; + break; + case "function": + m.test = expectation; + if (message) { + m.message = message; + } else { + m.message = "match(" + sinon.functionName(expectation) + ")"; + } + break; + default: + m.test = function (actual) { + return sinon.deepEqual(expectation, actual); + }; + } + if (!m.message) { + m.message = "match(" + expectation + ")"; + } + return m; + }; + + match.isMatcher = isMatcher; + + match.any = match(function () { + return true; + }, "any"); + + match.defined = match(function (actual) { + return actual !== null && actual !== undefined; + }, "defined"); + + match.truthy = match(function (actual) { + return !!actual; + }, "truthy"); + + match.falsy = match(function (actual) { + return !actual; + }, "falsy"); + + match.same = function (expectation) { + return match(function (actual) { + return expectation === actual; + }, "same(" + expectation + ")"); + }; + + match.typeOf = function (type) { + assertType(type, "string", "type"); + return match(function (actual) { + return sinon.typeOf(actual) === type; + }, "typeOf(\"" + type + "\")"); + }; + + match.instanceOf = function (type) { + assertType(type, "function", "type"); + return match(function (actual) { + return actual instanceof type; + }, "instanceOf(" + sinon.functionName(type) + ")"); + }; + + function createPropertyMatcher(propertyTest, messagePrefix) { + return function (property, value) { + assertType(property, "string", "property"); + var onlyProperty = arguments.length === 1; + var message = messagePrefix + "(\"" + property + "\""; + if (!onlyProperty) { + message += ", " + value; + } + message += ")"; + return match(function (actual) { + if (actual === undefined || actual === null || + !propertyTest(actual, property)) { + return false; + } + return onlyProperty || sinon.deepEqual(value, actual[property]); + }, message); + }; + } + + match.has = createPropertyMatcher(function (actual, property) { + if (typeof actual === "object") { + return property in actual; + } + return actual[property] !== undefined; + }, "has"); + + match.hasOwn = createPropertyMatcher(function (actual, property) { + return actual.hasOwnProperty(property); + }, "hasOwn"); + + match.bool = match.typeOf("boolean"); + match.number = match.typeOf("number"); + match.string = match.typeOf("string"); + match.object = match.typeOf("object"); + match.func = match.typeOf("function"); + match.array = match.typeOf("array"); + match.regexp = match.typeOf("regexp"); + match.date = match.typeOf("date"); + + if (commonJSModule) { + module.exports = match; + } else { + sinon.match = match; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend match.js + */ + /*jslint eqeqeq: false, onevar: false, plusplus: false*/ + /*global module, require, sinon*/ + /** + * Spy calls + * + * @author Christian Johansen (christian@cjohansen.no) + * @author Maximilian Antoni (mail@maxantoni.de) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + * Copyright (c) 2013 Maximilian Antoni + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function throwYieldError(proxy, text, args) { + var msg = sinon.functionName(proxy) + text; + if (args.length) { + msg += " Received [" + slice.call(args).join(", ") + "]"; + } + throw new Error(msg); + } + + var slice = Array.prototype.slice; + + var callProto = { + calledOn: function calledOn(thisValue) { + if (sinon.match && sinon.match.isMatcher(thisValue)) { + return thisValue.test(this.thisValue); + } + return this.thisValue === thisValue; + }, + + calledWith: function calledWith() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + if (!sinon.deepEqual(arguments[i], this.args[i])) { + return false; + } + } + + return true; + }, + + calledWithMatch: function calledWithMatch() { + for (var i = 0, l = arguments.length; i < l; i += 1) { + var actual = this.args[i]; + var expectation = arguments[i]; + if (!sinon.match || !sinon.match(expectation).test(actual)) { + return false; + } + } + return true; + }, + + calledWithExactly: function calledWithExactly() { + return arguments.length == this.args.length && + this.calledWith.apply(this, arguments); + }, + + notCalledWith: function notCalledWith() { + return !this.calledWith.apply(this, arguments); + }, + + notCalledWithMatch: function notCalledWithMatch() { + return !this.calledWithMatch.apply(this, arguments); + }, + + returned: function returned(value) { + return sinon.deepEqual(value, this.returnValue); + }, + + threw: function threw(error) { + if (typeof error === "undefined" || !this.exception) { + return !!this.exception; + } + + return this.exception === error || this.exception.name === error; + }, + + calledWithNew: function calledWithNew(thisValue) { + return this.thisValue instanceof this.proxy; + }, + + calledBefore: function (other) { + return this.callId < other.callId; + }, + + calledAfter: function (other) { + return this.callId > other.callId; + }, + + callArg: function (pos) { + this.args[pos](); + }, + + callArgOn: function (pos, thisValue) { + this.args[pos].apply(thisValue); + }, + + callArgWith: function (pos) { + this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1))); + }, + + callArgOnWith: function (pos, thisValue) { + var args = slice.call(arguments, 2); + this.args[pos].apply(thisValue, args); + }, + + "yield": function () { + this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0))); + }, + + yieldOn: function (thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (typeof args[i] === "function") { + args[i].apply(thisValue, slice.call(arguments, 1)); + return; + } + } + throwYieldError(this.proxy, " cannot yield since no callback was passed.", args); + }, + + yieldTo: function (prop) { + this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1))); + }, + + yieldToOn: function (prop, thisValue) { + var args = this.args; + for (var i = 0, l = args.length; i < l; ++i) { + if (args[i] && typeof args[i][prop] === "function") { + args[i][prop].apply(thisValue, slice.call(arguments, 2)); + return; + } + } + throwYieldError(this.proxy, " cannot yield to '" + prop + + "' since no callback was passed.", args); + }, + + toString: function () { + var callStr = this.proxy.toString() + "("; + var args = []; + + for (var i = 0, l = this.args.length; i < l; ++i) { + args.push(sinon.format(this.args[i])); + } + + callStr = callStr + args.join(", ") + ")"; + + if (typeof this.returnValue != "undefined") { + callStr += " => " + sinon.format(this.returnValue); + } + + if (this.exception) { + callStr += " !" + this.exception.name; + + if (this.exception.message) { + callStr += "(" + this.exception.message + ")"; + } + } + + return callStr; + } + }; + + callProto.invokeCallback = callProto.yield; + + function createSpyCall(spy, thisValue, args, returnValue, exception, id) { + if (typeof id !== "number") { + throw new TypeError("Call id is not a number"); + } + var proxyCall = sinon.create(callProto); + proxyCall.proxy = spy; + proxyCall.thisValue = thisValue; + proxyCall.args = args; + proxyCall.returnValue = returnValue; + proxyCall.exception = exception; + proxyCall.callId = id; + + return proxyCall; + }; + createSpyCall.toString = callProto.toString; // used by mocks + + if (commonJSModule) { + module.exports = createSpyCall; + } else { + sinon.spyCall = createSpyCall; + } + }(typeof sinon == "object" && sinon || null)); + + + /** + * @depend ../sinon.js + * @depend call.js + */ + /*jslint eqeqeq: false, onevar: false, plusplus: false*/ + /*global module, require, sinon*/ + /** + * Spy functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = Array.prototype.push; + var slice = Array.prototype.slice; + var callId = 0; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function spy(object, property) { + if (!property && typeof object == "function") { + return spy.create(object); + } + + if (!object && !property) { + return spy.create(function () { }); + } + + var method = object[property]; + return sinon.wrapMethod(object, property, spy.create(method)); + } + + function matchingFake(fakes, args, strict) { + if (!fakes) { + return; + } + + var alen = args.length; + + for (var i = 0, l = fakes.length; i < l; i++) { + if (fakes[i].matches(args, strict)) { + return fakes[i]; + } + } + } + + function incrementCallCount() { + this.called = true; + this.callCount += 1; + this.notCalled = false; + this.calledOnce = this.callCount == 1; + this.calledTwice = this.callCount == 2; + this.calledThrice = this.callCount == 3; + } + + function createCallProperties() { + this.firstCall = this.getCall(0); + this.secondCall = this.getCall(1); + this.thirdCall = this.getCall(2); + this.lastCall = this.getCall(this.callCount - 1); + } + + var vars = "a,b,c,d,e,f,g,h,i,j,k,l"; + function createProxy(func) { + // Retain the function length: + var p; + if (func.length) { + eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) + + ") { return p.invoke(func, this, slice.call(arguments)); });"); + } + else { + p = function proxy() { + return p.invoke(func, this, slice.call(arguments)); + }; + } + return p; + } + + var uuid = 0; + + // Public API + var spyApi = { + reset: function () { + this.called = false; + this.notCalled = true; + this.calledOnce = false; + this.calledTwice = false; + this.calledThrice = false; + this.callCount = 0; + this.firstCall = null; + this.secondCall = null; + this.thirdCall = null; + this.lastCall = null; + this.args = []; + this.returnValues = []; + this.thisValues = []; + this.exceptions = []; + this.callIds = []; + if (this.fakes) { + for (var i = 0; i < this.fakes.length; i++) { + this.fakes[i].reset(); + } + } + }, + + create: function create(func) { + var name; + + if (typeof func != "function") { + func = function () { }; + } else { + name = sinon.functionName(func); + } + + var proxy = createProxy(func); + + sinon.extend(proxy, spy); + delete proxy.create; + sinon.extend(proxy, func); + + proxy.reset(); + proxy.prototype = func.prototype; + proxy.displayName = name || "spy"; + proxy.toString = sinon.functionToString; + proxy._create = sinon.spy.create; + proxy.id = "spy#" + uuid++; + + return proxy; + }, + + invoke: function invoke(func, thisValue, args) { + var matching = matchingFake(this.fakes, args); + var exception, returnValue; + + incrementCallCount.call(this); + push.call(this.thisValues, thisValue); + push.call(this.args, args); + push.call(this.callIds, callId++); + + try { + if (matching) { + returnValue = matching.invoke(func, thisValue, args); + } else { + returnValue = (this.func || func).apply(thisValue, args); + } + } catch (e) { + push.call(this.returnValues, undefined); + exception = e; + throw e; + } finally { + push.call(this.exceptions, exception); + } + + push.call(this.returnValues, returnValue); + + createCallProperties.call(this); + + return returnValue; + }, + + getCall: function getCall(i) { + if (i < 0 || i >= this.callCount) { + return null; + } + + return sinon.spyCall(this, this.thisValues[i], this.args[i], + this.returnValues[i], this.exceptions[i], + this.callIds[i]); + }, + + calledBefore: function calledBefore(spyFn) { + if (!this.called) { + return false; + } + + if (!spyFn.called) { + return true; + } + + return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1]; + }, + + calledAfter: function calledAfter(spyFn) { + if (!this.called || !spyFn.called) { + return false; + } + + return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1]; + }, + + withArgs: function () { + var args = slice.call(arguments); + + if (this.fakes) { + var match = matchingFake(this.fakes, args, true); + + if (match) { + return match; + } + } else { + this.fakes = []; + } + + var original = this; + var fake = this._create(); + fake.matchingAguments = args; + push.call(this.fakes, fake); + + fake.withArgs = function () { + return original.withArgs.apply(original, arguments); + }; + + for (var i = 0; i < this.args.length; i++) { + if (fake.matches(this.args[i])) { + incrementCallCount.call(fake); + push.call(fake.thisValues, this.thisValues[i]); + push.call(fake.args, this.args[i]); + push.call(fake.returnValues, this.returnValues[i]); + push.call(fake.exceptions, this.exceptions[i]); + push.call(fake.callIds, this.callIds[i]); + } + } + createCallProperties.call(fake); + + return fake; + }, + + matches: function (args, strict) { + var margs = this.matchingAguments; + + if (margs.length <= args.length && + sinon.deepEqual(margs, args.slice(0, margs.length))) { + return !strict || margs.length == args.length; + } + }, + + printf: function (format) { + var spy = this; + var args = slice.call(arguments, 1); + var formatter; + + return (format || "").replace(/%(.)/g, function (match, specifyer) { + formatter = spyApi.formatters[specifyer]; + + if (typeof formatter == "function") { + return formatter.call(null, spy, args); + } else if (!isNaN(parseInt(specifyer), 10)) { + return sinon.format(args[specifyer - 1]); + } + + return "%" + specifyer; + }); + } + }; + + function delegateToCalls(method, matchAny, actual, notCalled) { + spyApi[method] = function () { + if (!this.called) { + if (notCalled) { + return notCalled.apply(this, arguments); + } + return false; + } + + var currentCall; + var matches = 0; + + for (var i = 0, l = this.callCount; i < l; i += 1) { + currentCall = this.getCall(i); + + if (currentCall[actual || method].apply(currentCall, arguments)) { + matches += 1; + + if (matchAny) { + return true; + } + } + } + + return matches === this.callCount; + }; + } + + delegateToCalls("calledOn", true); + delegateToCalls("alwaysCalledOn", false, "calledOn"); + delegateToCalls("calledWith", true); + delegateToCalls("calledWithMatch", true); + delegateToCalls("alwaysCalledWith", false, "calledWith"); + delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch"); + delegateToCalls("calledWithExactly", true); + delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly"); + delegateToCalls("neverCalledWith", false, "notCalledWith", + function () { return true; }); + delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", + function () { return true; }); + delegateToCalls("threw", true); + delegateToCalls("alwaysThrew", false, "threw"); + delegateToCalls("returned", true); + delegateToCalls("alwaysReturned", false, "returned"); + delegateToCalls("calledWithNew", true); + delegateToCalls("alwaysCalledWithNew", false, "calledWithNew"); + delegateToCalls("callArg", false, "callArgWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgWith = spyApi.callArg; + delegateToCalls("callArgOn", false, "callArgOnWith", function () { + throw new Error(this.toString() + " cannot call arg since it was not yet invoked."); + }); + spyApi.callArgOnWith = spyApi.callArgOn; + delegateToCalls("yield", false, "yield", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode. + spyApi.invokeCallback = spyApi.yield; + delegateToCalls("yieldOn", false, "yieldOn", function () { + throw new Error(this.toString() + " cannot yield since it was not yet invoked."); + }); + delegateToCalls("yieldTo", false, "yieldTo", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + delegateToCalls("yieldToOn", false, "yieldToOn", function (property) { + throw new Error(this.toString() + " cannot yield to '" + property + + "' since it was not yet invoked."); + }); + + spyApi.formatters = { + "c": function (spy) { + return sinon.timesInWords(spy.callCount); + }, + + "n": function (spy) { + return spy.toString(); + }, + + "C": function (spy) { + var calls = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + var stringifiedCall = " " + spy.getCall(i).toString(); + if (/\n/.test(calls[i - 1])) { + stringifiedCall = "\n" + stringifiedCall; + } + push.call(calls, stringifiedCall); + } + + return calls.length > 0 ? "\n" + calls.join("\n") : ""; + }, + + "t": function (spy) { + var objects = []; + + for (var i = 0, l = spy.callCount; i < l; ++i) { + push.call(objects, sinon.format(spy.thisValues[i])); + } + + return objects.join(", "); + }, + + "*": function (spy, args) { + var formatted = []; + + for (var i = 0, l = args.length; i < l; ++i) { + push.call(formatted, sinon.format(args[i])); + } + + return formatted.join(", "); + } + }; + + sinon.extend(spy, spyApi); + + spy.spyCall = sinon.spyCall; + + if (commonJSModule) { + module.exports = spy; + } else { + sinon.spy = spy; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend spy.js + */ + /*jslint eqeqeq: false, onevar: false*/ + /*global module, require, sinon*/ + /** + * Stub functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function stub(object, property, func) { + if (!!func && typeof func != "function") { + throw new TypeError("Custom stub should be function"); + } + + var wrapper; + + if (func) { + wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func; + } else { + wrapper = stub.create(); + } + + if (!object && !property) { + return sinon.stub.create(); + } + + if (!property && !!object && typeof object == "object") { + for (var prop in object) { + if (typeof object[prop] === "function") { + stub(object, prop); + } + } + + return object; + } + + return sinon.wrapMethod(object, property, wrapper); + } + + function getChangingValue(stub, property) { + var index = stub.callCount - 1; + var values = stub[property]; + var prop = index in values ? values[index] : values[values.length - 1]; + stub[property + "Last"] = prop; + + return prop; + } + + function getCallback(stub, args) { + var callArgAt = getChangingValue(stub, "callArgAts"); + + if (callArgAt < 0) { + var callArgProp = getChangingValue(stub, "callArgProps"); + + for (var i = 0, l = args.length; i < l; ++i) { + if (!callArgProp && typeof args[i] == "function") { + return args[i]; + } + + if (callArgProp && args[i] && + typeof args[i][callArgProp] == "function") { + return args[i][callArgProp]; + } + } + + return null; + } + + return args[callArgAt]; + } + + var join = Array.prototype.join; + + function getCallbackError(stub, func, args) { + if (stub.callArgAtsLast < 0) { + var msg; + + if (stub.callArgPropsLast) { + msg = sinon.functionName(stub) + + " expected to yield to '" + stub.callArgPropsLast + + "', but no object with such a property was passed." + } else { + msg = sinon.functionName(stub) + + " expected to yield, but no callback was passed." + } + + if (args.length > 0) { + msg += " Received [" + join.call(args, ", ") + "]"; + } + + return msg; + } + + return "argument at index " + stub.callArgAtsLast + " is not a function: " + func; + } + + var nextTick = (function () { + if (typeof process === "object" && typeof process.nextTick === "function") { + return process.nextTick; + } else if (typeof setImmediate === "function") { + return setImmediate; + } else { + return function (callback) { + setTimeout(callback, 0); + }; + } + })(); + + function callCallback(stub, args) { + if (stub.callArgAts.length > 0) { + var func = getCallback(stub, args); + + if (typeof func != "function") { + throw new TypeError(getCallbackError(stub, func, args)); + } + + var callbackArguments = getChangingValue(stub, "callbackArguments"); + var callbackContext = getChangingValue(stub, "callbackContexts"); + + if (stub.callbackAsync) { + nextTick(function() { + func.apply(callbackContext, callbackArguments); + }); + } else { + func.apply(callbackContext, callbackArguments); + } + } + } + + var uuid = 0; + + sinon.extend(stub, (function () { + var slice = Array.prototype.slice, proto; + + function throwsException(error, message) { + if (typeof error == "string") { + this.exception = new Error(message || ""); + this.exception.name = error; + } else if (!error) { + this.exception = new Error("Error"); + } else { + this.exception = error; + } + + return this; + } + + proto = { + create: function create() { + var functionStub = function () { + + callCallback(functionStub, arguments); + + if (functionStub.exception) { + throw functionStub.exception; + } else if (typeof functionStub.returnArgAt == 'number') { + return arguments[functionStub.returnArgAt]; + } else if (functionStub.returnThis) { + return this; + } + return functionStub.returnValue; + }; + + functionStub.id = "stub#" + uuid++; + var orig = functionStub; + functionStub = sinon.spy.create(functionStub); + functionStub.func = orig; + + functionStub.callArgAts = []; + functionStub.callbackArguments = []; + functionStub.callbackContexts = []; + functionStub.callArgProps = []; + + sinon.extend(functionStub, stub); + functionStub._create = sinon.stub.create; + functionStub.displayName = "stub"; + functionStub.toString = sinon.functionToString; + + return functionStub; + }, + + resetBehavior: function () { + var i; + + this.callArgAts = []; + this.callbackArguments = []; + this.callbackContexts = []; + this.callArgProps = []; + + delete this.returnValue; + delete this.returnArgAt; + this.returnThis = false; + + if (this.fakes) { + for (i = 0; i < this.fakes.length; i++) { + this.fakes[i].resetBehavior(); + } + } + }, + + returns: function returns(value) { + this.returnValue = value; + + return this; + }, + + returnsArg: function returnsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.returnArgAt = pos; + + return this; + }, + + returnsThis: function returnsThis() { + this.returnThis = true; + + return this; + }, + + "throws": throwsException, + throwsException: throwsException, + + callsArg: function callsArg(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOn: function callsArgOn(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push([]); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgWith: function callsArgWith(pos) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + callsArgOnWith: function callsArgWith(pos, context) { + if (typeof pos != "number") { + throw new TypeError("argument index is not number"); + } + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(pos); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yields: function () { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 0)); + this.callbackContexts.push(undefined); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsOn: function (context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(context); + this.callArgProps.push(undefined); + + return this; + }, + + yieldsTo: function (prop) { + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 1)); + this.callbackContexts.push(undefined); + this.callArgProps.push(prop); + + return this; + }, + + yieldsToOn: function (prop, context) { + if (typeof context != "object") { + throw new TypeError("argument context is not an object"); + } + + this.callArgAts.push(-1); + this.callbackArguments.push(slice.call(arguments, 2)); + this.callbackContexts.push(context); + this.callArgProps.push(prop); + + return this; + } + }; + + // create asynchronous versions of callsArg* and yields* methods + for (var method in proto) { + // need to avoid creating anotherasync versions of the newly added async methods + if (proto.hasOwnProperty(method) && + method.match(/^(callsArg|yields|thenYields$)/) && + !method.match(/Async/)) { + proto[method + 'Async'] = (function (syncFnName) { + return function () { + this.callbackAsync = true; + return this[syncFnName].apply(this, arguments); + }; + })(method); + } + } + + return proto; + + }())); + + if (commonJSModule) { + module.exports = stub; + } else { + sinon.stub = stub; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend stub.js + */ + /*jslint eqeqeq: false, onevar: false, nomen: false*/ + /*global module, require, sinon*/ + /** + * Mock functions. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function mock(object) { + if (!object) { + return sinon.expectation.create("Anonymous mock"); + } + + return mock.create(object); + } + + sinon.mock = mock; + + sinon.extend(mock, (function () { + function each(collection, callback) { + if (!collection) { + return; + } + + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + + return { + create: function create(object) { + if (!object) { + throw new TypeError("object is null"); + } + + var mockObject = sinon.extend({}, mock); + mockObject.object = object; + delete mockObject.create; + + return mockObject; + }, + + expects: function expects(method) { + if (!method) { + throw new TypeError("method is falsy"); + } + + if (!this.expectations) { + this.expectations = {}; + this.proxies = []; + } + + if (!this.expectations[method]) { + this.expectations[method] = []; + var mockObject = this; + + sinon.wrapMethod(this.object, method, function () { + return mockObject.invokeMethod(method, this, arguments); + }); + + push.call(this.proxies, method); + } + + var expectation = sinon.expectation.create(method); + push.call(this.expectations[method], expectation); + + return expectation; + }, + + restore: function restore() { + var object = this.object; + + each(this.proxies, function (proxy) { + if (typeof object[proxy].restore == "function") { + object[proxy].restore(); + } + }); + }, + + verify: function verify() { + var expectations = this.expectations || {}; + var messages = [], met = []; + + each(this.proxies, function (proxy) { + each(expectations[proxy], function (expectation) { + if (!expectation.met()) { + push.call(messages, expectation.toString()); + } else { + push.call(met, expectation.toString()); + } + }); + }); + + this.restore(); + + if (messages.length > 0) { + sinon.expectation.fail(messages.concat(met).join("\n")); + } else { + sinon.expectation.pass(messages.concat(met).join("\n")); + } + + return true; + }, + + invokeMethod: function invokeMethod(method, thisValue, args) { + var expectations = this.expectations && this.expectations[method]; + var length = expectations && expectations.length || 0, i; + + for (i = 0; i < length; i += 1) { + if (!expectations[i].met() && + expectations[i].allowsCall(thisValue, args)) { + return expectations[i].apply(thisValue, args); + } + } + + var messages = [], available, exhausted = 0; + + for (i = 0; i < length; i += 1) { + if (expectations[i].allowsCall(thisValue, args)) { + available = available || expectations[i]; + } else { + exhausted += 1; + } + push.call(messages, " " + expectations[i].toString()); + } + + if (exhausted === 0) { + return available.apply(thisValue, args); + } + + messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({ + proxy: method, + args: args + })); + + sinon.expectation.fail(messages.join("\n")); + } + }; + }())); + + var times = sinon.timesInWords; + + sinon.expectation = (function () { + var slice = Array.prototype.slice; + var _invoke = sinon.spy.invoke; + + function callCountInWords(callCount) { + if (callCount == 0) { + return "never called"; + } else { + return "called " + times(callCount); + } + } + + function expectedCallCountInWords(expectation) { + var min = expectation.minCalls; + var max = expectation.maxCalls; + + if (typeof min == "number" && typeof max == "number") { + var str = times(min); + + if (min != max) { + str = "at least " + str + " and at most " + times(max); + } + + return str; + } + + if (typeof min == "number") { + return "at least " + times(min); + } + + return "at most " + times(max); + } + + function receivedMinCalls(expectation) { + var hasMinLimit = typeof expectation.minCalls == "number"; + return !hasMinLimit || expectation.callCount >= expectation.minCalls; + } + + function receivedMaxCalls(expectation) { + if (typeof expectation.maxCalls != "number") { + return false; + } + + return expectation.callCount == expectation.maxCalls; + } + + return { + minCalls: 1, + maxCalls: 1, + + create: function create(methodName) { + var expectation = sinon.extend(sinon.stub.create(), sinon.expectation); + delete expectation.create; + expectation.method = methodName; + + return expectation; + }, + + invoke: function invoke(func, thisValue, args) { + this.verifyCallAllowed(thisValue, args); + + return _invoke.apply(this, arguments); + }, + + atLeast: function atLeast(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.maxCalls = null; + this.limitsSet = true; + } + + this.minCalls = num; + + return this; + }, + + atMost: function atMost(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not number"); + } + + if (!this.limitsSet) { + this.minCalls = null; + this.limitsSet = true; + } + + this.maxCalls = num; + + return this; + }, + + never: function never() { + return this.exactly(0); + }, + + once: function once() { + return this.exactly(1); + }, + + twice: function twice() { + return this.exactly(2); + }, + + thrice: function thrice() { + return this.exactly(3); + }, + + exactly: function exactly(num) { + if (typeof num != "number") { + throw new TypeError("'" + num + "' is not a number"); + } + + this.atLeast(num); + return this.atMost(num); + }, + + met: function met() { + return !this.failed && receivedMinCalls(this); + }, + + verifyCallAllowed: function verifyCallAllowed(thisValue, args) { + if (receivedMaxCalls(this)) { + this.failed = true; + sinon.expectation.fail(this.method + " already called " + times(this.maxCalls)); + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " + + this.expectedThis); + } + + if (!("expectedArguments" in this)) { + return; + } + + if (!args) { + sinon.expectation.fail(this.method + " received no arguments, expected " + + sinon.format(this.expectedArguments)); + } + + if (args.length < this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) + + "), expected " + sinon.format(this.expectedArguments)); + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) + + ", expected " + sinon.format(this.expectedArguments)); + } + } + }, + + allowsCall: function allowsCall(thisValue, args) { + if (this.met() && receivedMaxCalls(this)) { + return false; + } + + if ("expectedThis" in this && this.expectedThis !== thisValue) { + return false; + } + + if (!("expectedArguments" in this)) { + return true; + } + + args = args || []; + + if (args.length < this.expectedArguments.length) { + return false; + } + + if (this.expectsExactArgCount && + args.length != this.expectedArguments.length) { + return false; + } + + for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) { + if (!sinon.deepEqual(this.expectedArguments[i], args[i])) { + return false; + } + } + + return true; + }, + + withArgs: function withArgs() { + this.expectedArguments = slice.call(arguments); + return this; + }, + + withExactArgs: function withExactArgs() { + this.withArgs.apply(this, arguments); + this.expectsExactArgCount = true; + return this; + }, + + on: function on(thisValue) { + this.expectedThis = thisValue; + return this; + }, + + toString: function () { + var args = (this.expectedArguments || []).slice(); + + if (!this.expectsExactArgCount) { + push.call(args, "[...]"); + } + + var callStr = sinon.spyCall.toString.call({ + proxy: this.method || "anonymous mock expectation", + args: args + }); + + var message = callStr.replace(", [...", "[, ...") + " " + + expectedCallCountInWords(this); + + if (this.met()) { + return "Expectation met: " + message; + } + + return "Expected " + message + " (" + + callCountInWords(this.callCount) + ")"; + }, + + verify: function verify() { + if (!this.met()) { + sinon.expectation.fail(this.toString()); + } else { + sinon.expectation.pass(this.toString()); + } + + return true; + }, + + pass: function(message) { + sinon.assert.pass(message); + }, + fail: function (message) { + var exception = new Error(message); + exception.name = "ExpectationError"; + + throw exception; + } + }; + }()); + + if (commonJSModule) { + module.exports = mock; + } else { + sinon.mock = mock; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + */ + /*jslint eqeqeq: false, onevar: false, forin: true*/ + /*global module, require, sinon*/ + /** + * Collections of stubs, spies and mocks. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var push = [].push; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function getFakes(fakeCollection) { + if (!fakeCollection.fakes) { + fakeCollection.fakes = []; + } + + return fakeCollection.fakes; + } + + function each(fakeCollection, method) { + var fakes = getFakes(fakeCollection); + + for (var i = 0, l = fakes.length; i < l; i += 1) { + if (typeof fakes[i][method] == "function") { + fakes[i][method](); + } + } + } + + function compact(fakeCollection) { + var fakes = getFakes(fakeCollection); + var i = 0; + while (i < fakes.length) { + fakes.splice(i, 1); + } + } + + var collection = { + verify: function resolve() { + each(this, "verify"); + }, + + restore: function restore() { + each(this, "restore"); + compact(this); + }, + + verifyAndRestore: function verifyAndRestore() { + var exception; + + try { + this.verify(); + } catch (e) { + exception = e; + } + + this.restore(); + + if (exception) { + throw exception; + } + }, + + add: function add(fake) { + push.call(getFakes(this), fake); + return fake; + }, + + spy: function spy() { + return this.add(sinon.spy.apply(sinon, arguments)); + }, + + stub: function stub(object, property, value) { + if (property) { + var original = object[property]; + + if (typeof original != "function") { + if (!hasOwnProperty.call(object, property)) { + throw new TypeError("Cannot stub non-existent own property " + property); + } + + object[property] = value; + + return this.add({ + restore: function () { + object[property] = original; + } + }); + } + } + if (!property && !!object && typeof object == "object") { + var stubbedObj = sinon.stub.apply(sinon, arguments); + + for (var prop in stubbedObj) { + if (typeof stubbedObj[prop] === "function") { + this.add(stubbedObj[prop]); + } + } + + return stubbedObj; + } + + return this.add(sinon.stub.apply(sinon, arguments)); + }, + + mock: function mock() { + return this.add(sinon.mock.apply(sinon, arguments)); + }, + + inject: function inject(obj) { + var col = this; + + obj.spy = function () { + return col.spy.apply(col, arguments); + }; + + obj.stub = function () { + return col.stub.apply(col, arguments); + }; + + obj.mock = function () { + return col.mock.apply(col, arguments); + }; + + return obj; + } + }; + + if (commonJSModule) { + module.exports = collection; + } else { + sinon.collection = collection; + } + }(typeof sinon == "object" && sinon || null)); + + /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/ + /*global module, require, window*/ + /** + * Fake timer API + * setTimeout + * setInterval + * clearTimeout + * clearInterval + * tick + * reset + * Date + * + * Inspired by jsUnitMockTimeOut from JsUnit + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + if (typeof sinon == "undefined") { + var sinon = {}; + } + + (function (global) { + var id = 1; + + function addTimer(args, recurring) { + if (args.length === 0) { + throw new Error("Function requires at least 1 parameter"); + } + + var toId = id++; + var delay = args[1] || 0; + + if (!this.timeouts) { + this.timeouts = {}; + } + + this.timeouts[toId] = { + id: toId, + func: args[0], + callAt: this.now + delay, + invokeArgs: Array.prototype.slice.call(args, 2) + }; + + if (recurring === true) { + this.timeouts[toId].interval = delay; + } + + return toId; + } + + function parseTime(str) { + if (!str) { + return 0; + } + + var strings = str.split(":"); + var l = strings.length, i = l; + var ms = 0, parsed; + + if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { + throw new Error("tick only understands numbers and 'h:m:s'"); + } + + while (i--) { + parsed = parseInt(strings[i], 10); + + if (parsed >= 60) { + throw new Error("Invalid time " + str); + } + + ms += parsed * Math.pow(60, (l - i - 1)); + } + + return ms * 1000; + } + + function createObject(object) { + var newObject; + + if (Object.create) { + newObject = Object.create(object); + } else { + var F = function () {}; + F.prototype = object; + newObject = new F(); + } + + newObject.Date.clock = newObject; + return newObject; + } + + sinon.clock = { + now: 0, + + create: function create(now) { + var clock = createObject(this); + + if (typeof now == "number") { + clock.now = now; + } + + if (!!now && typeof now == "object") { + throw new TypeError("now should be milliseconds since UNIX epoch"); + } + + return clock; + }, + + setTimeout: function setTimeout(callback, timeout) { + return addTimer.call(this, arguments, false); + }, + + clearTimeout: function clearTimeout(timerId) { + if (!this.timeouts) { + this.timeouts = []; + } + + if (timerId in this.timeouts) { + delete this.timeouts[timerId]; + } + }, + + setInterval: function setInterval(callback, timeout) { + return addTimer.call(this, arguments, true); + }, + + clearInterval: function clearInterval(timerId) { + this.clearTimeout(timerId); + }, + + tick: function tick(ms) { + ms = typeof ms == "number" ? ms : parseTime(ms); + var tickFrom = this.now, tickTo = this.now + ms, previous = this.now; + var timer = this.firstTimerInRange(tickFrom, tickTo); + + var firstException; + while (timer && tickFrom <= tickTo) { + if (this.timeouts[timer.id]) { + tickFrom = this.now = timer.callAt; + try { + this.callTimer(timer); + } catch (e) { + firstException = firstException || e; + } + } + + timer = this.firstTimerInRange(previous, tickTo); + previous = tickFrom; + } + + this.now = tickTo; + + if (firstException) { + throw firstException; + } + + return this.now; + }, + + firstTimerInRange: function (from, to) { + var timer, smallest, originalTimer; + + for (var id in this.timeouts) { + if (this.timeouts.hasOwnProperty(id)) { + if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) { + continue; + } + + if (!smallest || this.timeouts[id].callAt < smallest) { + originalTimer = this.timeouts[id]; + smallest = this.timeouts[id].callAt; + + timer = { + func: this.timeouts[id].func, + callAt: this.timeouts[id].callAt, + interval: this.timeouts[id].interval, + id: this.timeouts[id].id, + invokeArgs: this.timeouts[id].invokeArgs + }; + } + } + } + + return timer || null; + }, + + callTimer: function (timer) { + if (typeof timer.interval == "number") { + this.timeouts[timer.id].callAt += timer.interval; + } else { + delete this.timeouts[timer.id]; + } + + try { + if (typeof timer.func == "function") { + timer.func.apply(null, timer.invokeArgs); + } else { + eval(timer.func); + } + } catch (e) { + var exception = e; + } + + if (!this.timeouts[timer.id]) { + if (exception) { + throw exception; + } + return; + } + + if (exception) { + throw exception; + } + }, + + reset: function reset() { + this.timeouts = {}; + }, + + Date: (function () { + var NativeDate = Date; + + function ClockDate(year, month, date, hour, minute, second, ms) { + // Defensive and verbose to avoid potential harm in passing + // explicit undefined when user does not pass argument + switch (arguments.length) { + case 0: + return new NativeDate(ClockDate.clock.now); + case 1: + return new NativeDate(year); + case 2: + return new NativeDate(year, month); + case 3: + return new NativeDate(year, month, date); + case 4: + return new NativeDate(year, month, date, hour); + case 5: + return new NativeDate(year, month, date, hour, minute); + case 6: + return new NativeDate(year, month, date, hour, minute, second); + default: + return new NativeDate(year, month, date, hour, minute, second, ms); + } + } + + return mirrorDateProperties(ClockDate, NativeDate); + }()) + }; + + function mirrorDateProperties(target, source) { + if (source.now) { + target.now = function now() { + return target.clock.now; + }; + } else { + delete target.now; + } + + if (source.toSource) { + target.toSource = function toSource() { + return source.toSource(); + }; + } else { + delete target.toSource; + } + + target.toString = function toString() { + return source.toString(); + }; + + target.prototype = source.prototype; + target.parse = source.parse; + target.UTC = source.UTC; + target.prototype.toUTCString = source.prototype.toUTCString; + return target; + } + + var methods = ["Date", "setTimeout", "setInterval", + "clearTimeout", "clearInterval"]; + + function restore() { + var method; + + for (var i = 0, l = this.methods.length; i < l; i++) { + method = this.methods[i]; + if (global[method].hadOwnProperty) { + global[method] = this["_" + method]; + } else { + delete global[method]; + } + } + + // Prevent multiple executions which will completely remove these props + this.methods = []; + } + + function stubGlobal(method, clock) { + clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method); + clock["_" + method] = global[method]; + + if (method == "Date") { + var date = mirrorDateProperties(clock[method], global[method]); + global[method] = date; + } else { + global[method] = function () { + return clock[method].apply(clock, arguments); + }; + + for (var prop in clock[method]) { + if (clock[method].hasOwnProperty(prop)) { + global[method][prop] = clock[method][prop]; + } + } + } + + global[method].clock = clock; + } + + sinon.useFakeTimers = function useFakeTimers(now) { + var clock = sinon.clock.create(now); + clock.restore = restore; + clock.methods = Array.prototype.slice.call(arguments, + typeof now == "number" ? 1 : 0); + + if (clock.methods.length === 0) { + clock.methods = methods; + } + + for (var i = 0, l = clock.methods.length; i < l; i++) { + stubGlobal(clock.methods[i], clock); + } + + return clock; + }; + }(typeof global != "undefined" && typeof global !== "function" ? global : this)); + + sinon.timers = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + Date: Date + }; + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; + } + + /*jslint eqeqeq: false, onevar: false*/ + /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ + /** + * Minimal Event interface implementation + * + * Original implementation by Sven Fuchs: https://gist.github.com/995028 + * Modifications and tests by Christian Johansen. + * + * @author Sven Fuchs (svenfuchs@artweb-design.de) + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2011 Sven Fuchs, Christian Johansen + */ + + if (typeof sinon == "undefined") { + this.sinon = {}; + } + + (function () { + var push = [].push; + + sinon.Event = function Event(type, bubbles, cancelable, target) { + this.initEvent(type, bubbles, cancelable, target); + }; + + sinon.Event.prototype = { + initEvent: function(type, bubbles, cancelable, target) { + this.type = type; + this.bubbles = bubbles; + this.cancelable = cancelable; + this.target = target; + }, + + stopPropagation: function () {}, + + preventDefault: function () { + this.defaultPrevented = true; + } + }; + + sinon.EventTarget = { + addEventListener: function addEventListener(event, listener, useCapture) { + this.eventListeners = this.eventListeners || {}; + this.eventListeners[event] = this.eventListeners[event] || []; + push.call(this.eventListeners[event], listener); + }, + + removeEventListener: function removeEventListener(event, listener, useCapture) { + var listeners = this.eventListeners && this.eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }, + + dispatchEvent: function dispatchEvent(event) { + var type = event.type; + var listeners = this.eventListeners && this.eventListeners[type] || []; + + for (var i = 0; i < listeners.length; i++) { + if (typeof listeners[i] == "function") { + listeners[i].call(this, event); + } else { + listeners[i].handleEvent(event); + } + } + + return !!event.defaultPrevented; + } + }; + }()); + + /** + * @depend ../../sinon.js + * @depend event.js + */ + /*jslint eqeqeq: false, onevar: false*/ + /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/ + /** + * Fake XMLHttpRequest object + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + if (typeof sinon == "undefined") { + this.sinon = {}; + } + sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest }; + +// wrapper for global + (function(global) { + var xhr = sinon.xhr; + xhr.GlobalXMLHttpRequest = global.XMLHttpRequest; + xhr.GlobalActiveXObject = global.ActiveXObject; + xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined"; + xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined"; + xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX + ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false; + + /*jsl:ignore*/ + var unsafeHeaders = { + "Accept-Charset": true, + "Accept-Encoding": true, + "Connection": true, + "Content-Length": true, + "Cookie": true, + "Cookie2": true, + "Content-Transfer-Encoding": true, + "Date": true, + "Expect": true, + "Host": true, + "Keep-Alive": true, + "Referer": true, + "TE": true, + "Trailer": true, + "Transfer-Encoding": true, + "Upgrade": true, + "User-Agent": true, + "Via": true + }; + /*jsl:end*/ + + function FakeXMLHttpRequest() { + this.readyState = FakeXMLHttpRequest.UNSENT; + this.requestHeaders = {}; + this.requestBody = null; + this.status = 0; + this.statusText = ""; + + var xhr = this; + + ["loadstart", "load", "abort", "loadend"].forEach(function (eventName) { + xhr.addEventListener(eventName, function (event) { + var listener = xhr["on" + eventName]; + + if (listener && typeof listener == "function") { + listener(event); + } + }); + }); + + if (typeof FakeXMLHttpRequest.onCreate == "function") { + FakeXMLHttpRequest.onCreate(this); + } + } + + function verifyState(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (xhr.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } + } + + // filtering to enable a white-list version of Sinon FakeXhr, + // where whitelisted requests are passed through to real XHR + function each(collection, callback) { + if (!collection) return; + for (var i = 0, l = collection.length; i < l; i += 1) { + callback(collection[i]); + } + } + function some(collection, callback) { + for (var index = 0; index < collection.length; index++) { + if(callback(collection[index]) === true) return true; + }; + return false; + } + // largest arity in XHR is 5 - XHR#open + var apply = function(obj,method,args) { + switch(args.length) { + case 0: return obj[method](); + case 1: return obj[method](args[0]); + case 2: return obj[method](args[0],args[1]); + case 3: return obj[method](args[0],args[1],args[2]); + case 4: return obj[method](args[0],args[1],args[2],args[3]); + case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]); + }; + }; + + FakeXMLHttpRequest.filters = []; + FakeXMLHttpRequest.addFilter = function(fn) { + this.filters.push(fn) + }; + var IE6Re = /MSIE 6/; + FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) { + var xhr = new sinon.xhr.workingXHR(); + each(["open","setRequestHeader","send","abort","getResponseHeader", + "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"], + function(method) { + fakeXhr[method] = function() { + return apply(xhr,method,arguments); + }; + }); + + var copyAttrs = function(args) { + each(args, function(attr) { + try { + fakeXhr[attr] = xhr[attr] + } catch(e) { + if(!IE6Re.test(navigator.userAgent)) throw e; + } + }); + }; + + var stateChange = function() { + fakeXhr.readyState = xhr.readyState; + if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) { + copyAttrs(["status","statusText"]); + } + if(xhr.readyState >= FakeXMLHttpRequest.LOADING) { + copyAttrs(["responseText"]); + } + if(xhr.readyState === FakeXMLHttpRequest.DONE) { + copyAttrs(["responseXML"]); + } + if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr); + }; + if(xhr.addEventListener) { + for(var event in fakeXhr.eventListeners) { + if(fakeXhr.eventListeners.hasOwnProperty(event)) { + each(fakeXhr.eventListeners[event],function(handler) { + xhr.addEventListener(event, handler); + }); + } + } + xhr.addEventListener("readystatechange",stateChange); + } else { + xhr.onreadystatechange = stateChange; + } + apply(xhr,"open",xhrArgs); + }; + FakeXMLHttpRequest.useFilters = false; + + function verifyRequestSent(xhr) { + if (xhr.readyState == FakeXMLHttpRequest.DONE) { + throw new Error("Request done"); + } + } + + function verifyHeadersReceived(xhr) { + if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { + throw new Error("No headers received"); + } + } + + function verifyResponseBodyType(body) { + if (typeof body != "string") { + var error = new Error("Attempted to respond to fake XMLHttpRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } + + sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, { + async: true, + + open: function open(method, url, async, username, password) { + this.method = method; + this.url = url; + this.async = typeof async == "boolean" ? async : true; + this.username = username; + this.password = password; + this.responseText = null; + this.responseXML = null; + this.requestHeaders = {}; + this.sendFlag = false; + if(sinon.FakeXMLHttpRequest.useFilters === true) { + var xhrArgs = arguments; + var defake = some(FakeXMLHttpRequest.filters,function(filter) { + return filter.apply(this,xhrArgs) + }); + if (defake) { + return sinon.FakeXMLHttpRequest.defake(this,arguments); + } + } + this.readyStateChange(FakeXMLHttpRequest.OPENED); + }, + + readyStateChange: function readyStateChange(state) { + this.readyState = state; + + if (typeof this.onreadystatechange == "function") { + try { + this.onreadystatechange(); + } catch (e) { + sinon.logError("Fake XHR onreadystatechange handler", e); + } + } + + this.dispatchEvent(new sinon.Event("readystatechange")); + + switch (this.readyState) { + case FakeXMLHttpRequest.DONE: + this.dispatchEvent(new sinon.Event("load", false, false, this)); + this.dispatchEvent(new sinon.Event("loadend", false, false, this)); + break; + } + }, + + setRequestHeader: function setRequestHeader(header, value) { + verifyState(this); + + if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { + throw new Error("Refused to set unsafe header \"" + header + "\""); + } + + if (this.requestHeaders[header]) { + this.requestHeaders[header] += "," + value; + } else { + this.requestHeaders[header] = value; + } + }, + + // Helps testing + setResponseHeaders: function setResponseHeaders(headers) { + this.responseHeaders = {}; + + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + this.responseHeaders[header] = headers[header]; + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); + } else { + this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; + } + }, + + // Currently treats ALL data as a DOMString (i.e. no Document) + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + if (this.requestHeaders["Content-Type"]) { + var value = this.requestHeaders["Content-Type"].split(";"); + this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; + } else { + this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + } + + this.requestBody = data; + } + + this.errorFlag = false; + this.sendFlag = this.async; + this.readyStateChange(FakeXMLHttpRequest.OPENED); + + if (typeof this.onSend == "function") { + this.onSend(this); + } + + this.dispatchEvent(new sinon.Event("loadstart", false, false, this)); + }, + + abort: function abort() { + this.aborted = true; + this.responseText = null; + this.errorFlag = true; + this.requestHeaders = {}; + + if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) { + this.readyStateChange(sinon.FakeXMLHttpRequest.DONE); + this.sendFlag = false; + } + + this.readyState = sinon.FakeXMLHttpRequest.UNSENT; + + this.dispatchEvent(new sinon.Event("abort", false, false, this)); + if (typeof this.onerror === "function") { + this.onerror(); + } + }, + + getResponseHeader: function getResponseHeader(header) { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return null; + } + + if (/^Set-Cookie2?$/i.test(header)) { + return null; + } + + header = header.toLowerCase(); + + for (var h in this.responseHeaders) { + if (h.toLowerCase() == header) { + return this.responseHeaders[h]; + } + } + + return null; + }, + + getAllResponseHeaders: function getAllResponseHeaders() { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return ""; + } + + var headers = ""; + + for (var header in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(header) && + !/^Set-Cookie2?$/i.test(header)) { + headers += header + ": " + this.responseHeaders[header] + "\r\n"; + } + } + + return headers; + }, + + setResponseBody: function setResponseBody(body) { + verifyRequestSent(this); + verifyHeadersReceived(this); + verifyResponseBodyType(body); + + var chunkSize = this.chunkSize || 10; + var index = 0; + this.responseText = ""; + + do { + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.LOADING); + } + + this.responseText += body.substring(index, index + chunkSize); + index += chunkSize; + } while (index < body.length); + + var type = this.getResponseHeader("Content-Type"); + + if (this.responseText && + (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { + try { + this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText); + } catch (e) { + // Unable to parse XML - no biggie + } + } + + if (this.async) { + this.readyStateChange(FakeXMLHttpRequest.DONE); + } else { + this.readyState = FakeXMLHttpRequest.DONE; + } + }, + + respond: function respond(status, headers, body) { + this.setResponseHeaders(headers || {}); + this.status = typeof status == "number" ? status : 200; + this.statusText = FakeXMLHttpRequest.statusCodes[this.status]; + this.setResponseBody(body || ""); + if (typeof this.onload === "function"){ + this.onload(); + } + + } + }); + + sinon.extend(FakeXMLHttpRequest, { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 + }); + + // Borrowed from JSpec + FakeXMLHttpRequest.parseXML = function parseXML(text) { + var xmlDoc; + + if (typeof DOMParser != "undefined") { + var parser = new DOMParser(); + xmlDoc = parser.parseFromString(text, "text/xml"); + } else { + xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(text); + } + + return xmlDoc; + }; + + FakeXMLHttpRequest.statusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 300: "Multiple Choice", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 422: "Unprocessable Entity", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported" + }; + + sinon.useFakeXMLHttpRequest = function () { + sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) { + if (xhr.supportsXHR) { + global.XMLHttpRequest = xhr.GlobalXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = xhr.GlobalActiveXObject; + } + + delete sinon.FakeXMLHttpRequest.restore; + + if (keepOnCreate !== true) { + delete sinon.FakeXMLHttpRequest.onCreate; + } + }; + if (xhr.supportsXHR) { + global.XMLHttpRequest = sinon.FakeXMLHttpRequest; + } + + if (xhr.supportsActiveX) { + global.ActiveXObject = function ActiveXObject(objId) { + if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) { + + return new sinon.FakeXMLHttpRequest(); + } + + return new xhr.GlobalActiveXObject(objId); + }; + } + + return sinon.FakeXMLHttpRequest; + }; + + sinon.FakeXMLHttpRequest = FakeXMLHttpRequest; + })(this); + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; + } + + /** + * @depend fake_xml_http_request.js + */ + /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/ + /*global module, require, window*/ + /** + * The Sinon "server" mimics a web server that receives requests from + * sinon.FakeXMLHttpRequest and provides an API to respond to those requests, + * both synchronously and asynchronously. To respond synchronuously, canned + * answers have to be provided upfront. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + if (typeof sinon == "undefined") { + var sinon = {}; + } + + sinon.fakeServer = (function () { + var push = [].push; + function F() {} + + function create(proto) { + F.prototype = proto; + return new F(); + } + + function responseArray(handler) { + var response = handler; + + if (Object.prototype.toString.call(handler) != "[object Array]") { + response = [200, {}, handler]; + } + + if (typeof response[2] != "string") { + throw new TypeError("Fake server response body should be string, but was " + + typeof response[2]); + } + + return response; + } + + var wloc = typeof window !== "undefined" ? window.location : {}; + var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host); + + function matchOne(response, reqMethod, reqUrl) { + var rmeth = response.method; + var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase(); + var url = response.url; + var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl)); + + return matchMethod && matchUrl; + } + + function match(response, request) { + var requestMethod = this.getHTTPMethod(request); + var requestUrl = request.url; + + if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) { + requestUrl = requestUrl.replace(rCurrLoc, ""); + } + + if (matchOne(response, this.getHTTPMethod(request), requestUrl)) { + if (typeof response.response == "function") { + var ru = response.url; + var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1)); + return response.response.apply(response, args); + } + + return true; + } + + return false; + } + + function log(response, request) { + var str; + + str = "Request:\n" + sinon.format(request) + "\n\n"; + str += "Response:\n" + sinon.format(response) + "\n\n"; + + sinon.log(str); + } + + return { + create: function () { + var server = create(this); + this.xhr = sinon.useFakeXMLHttpRequest(); + server.requests = []; + + this.xhr.onCreate = function (xhrObj) { + server.addRequest(xhrObj); + }; + + return server; + }, + + addRequest: function addRequest(xhrObj) { + var server = this; + push.call(this.requests, xhrObj); + + xhrObj.onSend = function () { + server.handleRequest(this); + }; + + if (this.autoRespond && !this.responding) { + setTimeout(function () { + server.responding = false; + server.respond(); + }, this.autoRespondAfter || 10); + + this.responding = true; + } + }, + + getHTTPMethod: function getHTTPMethod(request) { + if (this.fakeHTTPMethods && /post/i.test(request.method)) { + var matches = (request.requestBody || "").match(/_method=([^\b;]+)/); + return !!matches ? matches[1] : request.method; + } + + return request.method; + }, + + handleRequest: function handleRequest(xhr) { + if (xhr.async) { + if (!this.queue) { + this.queue = []; + } + + push.call(this.queue, xhr); + } else { + this.processRequest(xhr); + } + }, + + respondWith: function respondWith(method, url, body) { + if (arguments.length == 1 && typeof method != "function") { + this.response = responseArray(method); + return; + } + + if (!this.responses) { this.responses = []; } + + if (arguments.length == 1) { + body = method; + url = method = null; + } + + if (arguments.length == 2) { + body = url; + url = method; + method = null; + } + + push.call(this.responses, { + method: method, + url: url, + response: typeof body == "function" ? body : responseArray(body) + }); + }, + + respond: function respond() { + if (arguments.length > 0) this.respondWith.apply(this, arguments); + var queue = this.queue || []; + var request; + + while(request = queue.shift()) { + this.processRequest(request); + } + }, + + processRequest: function processRequest(request) { + try { + if (request.aborted) { + return; + } + + var response = this.response || [404, {}, ""]; + + if (this.responses) { + for (var i = 0, l = this.responses.length; i < l; i++) { + if (match.call(this, this.responses[i], request)) { + response = this.responses[i].response; + break; + } + } + } + + if (request.readyState != 4) { + log(response, request); + + request.respond(response[0], response[1], response[2]); + } + } catch (e) { + sinon.logError("Fake server request processing", e); + } + }, + + restore: function restore() { + return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments); + } + }; + }()); + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon; + } + + /** + * @depend fake_server.js + * @depend fake_timers.js + */ + /*jslint browser: true, eqeqeq: false, onevar: false*/ + /*global sinon*/ + /** + * Add-on for sinon.fakeServer that automatically handles a fake timer along with + * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery + * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead, + * it polls the object for completion with setInterval. Dispite the direct + * motivation, there is nothing jQuery-specific in this file, so it can be used + * in any environment where the ajax implementation depends on setInterval or + * setTimeout. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function () { + function Server() {} + Server.prototype = sinon.fakeServer; + + sinon.fakeServerWithClock = new Server(); + + sinon.fakeServerWithClock.addRequest = function addRequest(xhr) { + if (xhr.async) { + if (typeof setTimeout.clock == "object") { + this.clock = setTimeout.clock; + } else { + this.clock = sinon.useFakeTimers(); + this.resetClock = true; + } + + if (!this.longestTimeout) { + var clockSetTimeout = this.clock.setTimeout; + var clockSetInterval = this.clock.setInterval; + var server = this; + + this.clock.setTimeout = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetTimeout.apply(this, arguments); + }; + + this.clock.setInterval = function (fn, timeout) { + server.longestTimeout = Math.max(timeout, server.longestTimeout || 0); + + return clockSetInterval.apply(this, arguments); + }; + } + } + + return sinon.fakeServer.addRequest.call(this, xhr); + }; + + sinon.fakeServerWithClock.respond = function respond() { + var returnVal = sinon.fakeServer.respond.apply(this, arguments); + + if (this.clock) { + this.clock.tick(this.longestTimeout || 0); + this.longestTimeout = 0; + + if (this.resetClock) { + this.clock.restore(); + this.resetClock = false; + } + } + + return returnVal; + }; + + sinon.fakeServerWithClock.restore = function restore() { + if (this.clock) { + this.clock.restore(); + } + + return sinon.fakeServer.restore.apply(this, arguments); + }; + }()); + + /** + * @depend ../sinon.js + * @depend collection.js + * @depend util/fake_timers.js + * @depend util/fake_server_with_clock.js + */ + /*jslint eqeqeq: false, onevar: false, plusplus: false*/ + /*global require, module*/ + /** + * Manages fake collections as well as fake utilities such as Sinon's + * timers and fake XHR implementation in one convenient object. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + if (typeof module == "object" && typeof require == "function") { + var sinon = require("../sinon"); + sinon.extend(sinon, require("./util/fake_timers")); + } + + (function () { + var push = [].push; + + function exposeValue(sandbox, config, key, value) { + if (!value) { + return; + } + + if (config.injectInto) { + config.injectInto[key] = value; + } else { + push.call(sandbox.args, value); + } + } + + function prepareSandboxFromConfig(config) { + var sandbox = sinon.create(sinon.sandbox); + + if (config.useFakeServer) { + if (typeof config.useFakeServer == "object") { + sandbox.serverPrototype = config.useFakeServer; + } + + sandbox.useFakeServer(); + } + + if (config.useFakeTimers) { + if (typeof config.useFakeTimers == "object") { + sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers); + } else { + sandbox.useFakeTimers(); + } + } + + return sandbox; + } + + sinon.sandbox = sinon.extend(sinon.create(sinon.collection), { + useFakeTimers: function useFakeTimers() { + this.clock = sinon.useFakeTimers.apply(sinon, arguments); + + return this.add(this.clock); + }, + + serverPrototype: sinon.fakeServer, + + useFakeServer: function useFakeServer() { + var proto = this.serverPrototype || sinon.fakeServer; + + if (!proto || !proto.create) { + return null; + } + + this.server = proto.create(); + return this.add(this.server); + }, + + inject: function (obj) { + sinon.collection.inject.call(this, obj); + + if (this.clock) { + obj.clock = this.clock; + } + + if (this.server) { + obj.server = this.server; + obj.requests = this.server.requests; + } + + return obj; + }, + + create: function (config) { + if (!config) { + return sinon.create(sinon.sandbox); + } + + var sandbox = prepareSandboxFromConfig(config); + sandbox.args = sandbox.args || []; + var prop, value, exposed = sandbox.inject({}); + + if (config.properties) { + for (var i = 0, l = config.properties.length; i < l; i++) { + prop = config.properties[i]; + value = exposed[prop] || prop == "sandbox" && sandbox; + exposeValue(sandbox, config, prop, value); + } + } else { + exposeValue(sandbox, config, "sandbox", value); + } + + return sandbox; + } + }); + + sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer; + + if (typeof module == "object" && typeof require == "function") { + module.exports = sinon.sandbox; + } + }()); + + /** + * @depend ../sinon.js + * @depend stub.js + * @depend mock.js + * @depend sandbox.js + */ + /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/ + /*global module, require, sinon*/ + /** + * Test function, sandboxes fakes + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function test(callback) { + var type = typeof callback; + + if (type != "function") { + throw new TypeError("sinon.test needs to wrap a test function, got " + type); + } + + return function () { + var config = sinon.getConfig(sinon.config); + config.injectInto = config.injectIntoThis && this || config.injectInto; + var sandbox = sinon.sandbox.create(config); + var exception, result; + var args = Array.prototype.slice.call(arguments).concat(sandbox.args); + + try { + result = callback.apply(this, args); + } catch (e) { + exception = e; + } + + if (typeof exception !== "undefined") { + sandbox.restore(); + throw exception; + } + else { + sandbox.verifyAndRestore(); + } + + return result; + }; + } + + test.config = { + injectIntoThis: true, + injectInto: null, + properties: ["spy", "stub", "mock", "clock", "server", "requests"], + useFakeTimers: true, + useFakeServer: true + }; + + if (commonJSModule) { + module.exports = test; + } else { + sinon.test = test; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend test.js + */ + /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/ + /*global module, require, sinon*/ + /** + * Test case, sandboxes all test functions + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon || !Object.prototype.hasOwnProperty) { + return; + } + + function createTest(property, setUp, tearDown) { + return function () { + if (setUp) { + setUp.apply(this, arguments); + } + + var exception, result; + + try { + result = property.apply(this, arguments); + } catch (e) { + exception = e; + } + + if (tearDown) { + tearDown.apply(this, arguments); + } + + if (exception) { + throw exception; + } + + return result; + }; + } + + function testCase(tests, prefix) { + /*jsl:ignore*/ + if (!tests || typeof tests != "object") { + throw new TypeError("sinon.testCase needs an object with test functions"); + } + /*jsl:end*/ + + prefix = prefix || "test"; + var rPrefix = new RegExp("^" + prefix); + var methods = {}, testName, property, method; + var setUp = tests.setUp; + var tearDown = tests.tearDown; + + for (testName in tests) { + if (tests.hasOwnProperty(testName)) { + property = tests[testName]; + + if (/^(setUp|tearDown)$/.test(testName)) { + continue; + } + + if (typeof property == "function" && rPrefix.test(testName)) { + method = property; + + if (setUp || tearDown) { + method = createTest(property, setUp, tearDown); + } + + methods[testName] = sinon.test(method); + } else { + methods[testName] = tests[testName]; + } + } + } + + return methods; + } + + if (commonJSModule) { + module.exports = testCase; + } else { + sinon.testCase = testCase; + } + }(typeof sinon == "object" && sinon || null)); + + /** + * @depend ../sinon.js + * @depend stub.js + */ + /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/ + /*global module, require, sinon*/ + /** + * Assertions matching the test spy retrieval interface. + * + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2010-2013 Christian Johansen + */ + + (function (sinon, global) { + var commonJSModule = typeof module == "object" && typeof require == "function"; + var slice = Array.prototype.slice; + var assert; + + if (!sinon && commonJSModule) { + sinon = require("../sinon"); + } + + if (!sinon) { + return; + } + + function verifyIsStub() { + var method; + + for (var i = 0, l = arguments.length; i < l; ++i) { + method = arguments[i]; + + if (!method) { + assert.fail("fake is not a spy"); + } + + if (typeof method != "function") { + assert.fail(method + " is not a function"); + } + + if (typeof method.getCall != "function") { + assert.fail(method + " is not stubbed"); + } + } + } + + function failAssertion(object, msg) { + object = object || global; + var failMethod = object.fail || assert.fail; + failMethod.call(object, msg); + } + + function mirrorPropAsAssertion(name, method, message) { + if (arguments.length == 2) { + message = method; + method = name; + } + + assert[name] = function (fake) { + verifyIsStub(fake); + + var args = slice.call(arguments, 1); + var failed = false; + + if (typeof method == "function") { + failed = !method(fake); + } else { + failed = typeof fake[method] == "function" ? + !fake[method].apply(fake, args) : !fake[method]; + } + + if (failed) { + failAssertion(this, fake.printf.apply(fake, [message].concat(args))); + } else { + assert.pass(name); + } + }; + } + + function exposedName(prefix, prop) { + return !prefix || /^fail/.test(prop) ? prop : + prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1); + }; + + assert = { + failException: "AssertError", + + fail: function fail(message) { + var error = new Error(message); + error.name = this.failException || assert.failException; + + throw error; + }, + + pass: function pass(assertion) {}, + + callOrder: function assertCallOrder() { + verifyIsStub.apply(null, arguments); + var expected = "", actual = ""; + + if (!sinon.calledInOrder(arguments)) { + try { + expected = [].join.call(arguments, ", "); + var calls = slice.call(arguments); + var i = calls.length; + while (i) { + if (!calls[--i].called) { + calls.splice(i, 1); + } + } + actual = sinon.orderByFirstCall(calls).join(", "); + } catch (e) { + // If this fails, we'll just fall back to the blank string + } + + failAssertion(this, "expected " + expected + " to be " + + "called in order but were called as " + actual); + } else { + assert.pass("callOrder"); + } + }, + + callCount: function assertCallCount(method, count) { + verifyIsStub(method); + + if (method.callCount != count) { + var msg = "expected %n to be called " + sinon.timesInWords(count) + + " but was called %c%C"; + failAssertion(this, method.printf(msg)); + } else { + assert.pass("callCount"); + } + }, + + expose: function expose(target, options) { + if (!target) { + throw new TypeError("target is null or undefined"); + } + + var o = options || {}; + var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix; + var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail; + + for (var method in this) { + if (method != "export" && (includeFail || !/^(fail)/.test(method))) { + target[exposedName(prefix, method)] = this[method]; + } + } + + return target; + } + }; + + mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called"); + mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; }, + "expected %n to not have been called but was called %c%C"); + mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C"); + mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C"); + mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C"); + mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t"); + mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new"); + mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new"); + mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C"); + mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C"); + mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C"); + mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C"); + mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C"); + mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C"); + mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C"); + mirrorPropAsAssertion("threw", "%n did not throw exception%C"); + mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C"); + + if (commonJSModule) { + module.exports = assert; + } else { + sinon.assert = assert; + } + }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global)); + + return sinon;}.call(typeof window != 'undefined' && window || {})); diff --git a/test/lib/underscore.js b/test/lib/underscore.js new file mode 100644 index 0000000..4e38c1e --- /dev/null +++ b/test/lib/underscore.js @@ -0,0 +1,6 @@ +// Underscore.js 1.5.2 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); +//# sourceMappingURL=underscore-min.map diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js new file mode 100644 index 0000000..fc277bc --- /dev/null +++ b/test/spec/bindingSpec.js @@ -0,0 +1,69 @@ +// use http://jonathan.tang.name/files/js_keycode/test_keycode.html +// to discover key codes +describe("binding functions to key combinations", function() { + + beforeEach(function() { + + this.callbackFn = function(e) {}; + + spyOn(this, 'callbackFn'); + this.fixture = $('
'); + $('body').append(this.fixture); + + this.createInputEl = function(type, id) { + this.fixture.append(''); + }; + + this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", + "time", "datetime", "datetime-local", "search", "color", "tel"]; + + // creates new key event + this.createKeyEvent = function(keycode, namespace) { + + var eventName = "keyup"; + if (namespace) { + eventName += '.' + namespace; + } + + var event = jQuery.Event(eventName); + event.keycode = keycode; + event.which = keycode; + + return event; + }; + }); + + afterEach(function(){ + $('#container').remove(); + $(document).unbind(); + }); + + it("should bind the 'return' hotkey event to the document and trigger the bound callback", function() { + + this.createInputEl('text', '01'); + $(document).bind('keyup', 'return', this.callbackFn); + $(document).trigger(this.createKeyEvent('13')); + expect(this.callbackFn).toHaveBeenCalled(); + }); + + it("should bind 'Ctrl+a' and call the bound callback function", function() { + + this.createInputEl('text', '01'); + $(document).bind('keyup', 'Ctrl+a', this.callbackFn); + + var event = this.createKeyEvent('65'); + event.ctrlKey = true; + $(document).trigger(event); + expect(this.callbackFn).toHaveBeenCalled(); + }); + + it("should bind to 'Alt+a' and call the bound callback function", function() { + + this.createInputEl('text', '01'); + $(document).bind('keyup', 'Alt+a', this.callbackFn); + var event = this.createKeyEvent('65'); + event.altKey = true; + $(document).trigger(event); + expect(this.callbackFn).toHaveBeenCalled(); + }); +}); diff --git a/travis.yml b/travis.yml new file mode 100644 index 0000000..a5ef57a --- /dev/null +++ b/travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "0.10" +before_script: + - "npm i -g" +script: + - "grunt -v" \ No newline at end of file From 42a2334144fcb3db2ba1aaab734c6f51203c7a60 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 4 Feb 2014 14:36:47 +1100 Subject: [PATCH 21/59] Renamed travis.yml to correct name for travis-CI. --- travis.yml => .travis.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename travis.yml => .travis.yml (100%) diff --git a/travis.yml b/.travis.yml similarity index 100% rename from travis.yml rename to .travis.yml From 04e4317f5fccaf8fa2bfb2ed2ccca8ccea82f5f4 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Wed, 5 Feb 2014 11:42:32 +1100 Subject: [PATCH 22/59] Touched Gruntfile.js to trigger initial travis build. --- Gruntfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index ed36999..06bf1ea 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,5 +30,4 @@ module.exports = function(grunt) { // Task registration. grunt.registerTask("default", ["jshint", "jasmine"]); - }; From d971a5a1b9fac826355c7bab6e95ca1870dd61ba Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Wed, 5 Feb 2014 15:21:25 +1100 Subject: [PATCH 23/59] Updated travis dependencies and build task. --- .travis.yml | 6 +----- package.json | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5ef57a..fdb4a91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,3 @@ language: node_js node_js: - - "0.10" -before_script: - - "npm i -g" -script: - - "grunt -v" \ No newline at end of file + - "0.10" \ No newline at end of file diff --git a/package.json b/package.json index 43513ac..1c95b62 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,8 @@ "type": "git", "url": "https://github.com/jeresig/jquery.hotkeys" }, - "license": "MIT or GPL Version 2" + "license": "MIT or GPL Version 2", + "scripts": { + "test": "grunt -v" + } } From 74d688201b08ee6dc0d013160bd804ef72ac0695 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Wed, 5 Feb 2014 16:38:43 +1100 Subject: [PATCH 24/59] Getting grunt to run in Travis-CI Environment. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fdb4a91..b0edbcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: node_js node_js: - - "0.10" \ No newline at end of file + - "0.10" +before_script: + - npm install -g grunt-cli \ No newline at end of file From 1a3ca48de7369d1a2851c6f99dc61edb660bb622 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 11 Feb 2014 23:39:42 +1100 Subject: [PATCH 25/59] Added more jasmine tests, grunt watch task and updated README. --- .gitignore | 1 + Gruntfile.js | 22 ++++++-- README.md | 23 ++++++--- test/SpecRunner.html | 43 +++++++++++----- test/spec/bindingSpec.js | 107 +++++++++++++++++++++++++++++---------- 5 files changed, 143 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index eb79dd5..b7c33b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .idea +.grunt \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 06bf1ea..f4ee345 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,19 +8,34 @@ module.exports = function(grunt) { "unused": true }, - files: ["jquery.hotkeys.js"] + files: 'jquery.hotkeys.js' }, jasmine: { pivotal: { - src: 'test/lib/**.js', + src: 'jquery.hotkeys.js', options: { - vendor: 'jquery-1.4.2.js', + vendor: ['jquery-1.4.2.js', 'test/lib/**.js'], + outfile: 'test/SpecRunner.html', + keepRunner: true, specs: 'test/spec/*Spec.js' } } + }, + watch: { + scripts: { + files: ['**/*.js'], + tasks: ['default'], + options: { + spawn: false, + interrupt: true, + debounceDelay: 1000 + } + } } }); + grunt.loadNpmTasks('grunt-contrib-watch'); + // Task loading. grunt.loadNpmTasks("grunt-contrib-jshint"); @@ -29,5 +44,4 @@ module.exports = function(grunt) { // Task registration. grunt.registerTask("default", ["jshint", "jasmine"]); - }; diff --git a/README.md b/README.md index 898ebcf..3e06b42 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # jQuery.Hotkeys [![Build Status](https://secure.travis-ci.org/jeresig/jquery.hotkeys.png)](http://travis-ci.org/jeresig/jquery.hotkeys) #About -**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. +**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination. This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](https://github.com/tzuryby/jquery.hotkeys) @@ -30,25 +30,34 @@ Syntax when wanting to use jQuery's `on()`/`off` methods: }); ## Types -Supported types are `'keydown'`, `'keyup'` and `'keypress'` + +Supported types are `'keydown'`, `'keyup'` and `'keypress'` + +## Example + +[Example] http://htmlpreview.github.com/?https://github.com/jeresig/jquery.hotkeys/master/test-static-05.html ## Notes +Modifiers are not case sensitive (Ctrl == ctrl == cTRL) + If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing. +You can use namespacing by adding a suffix to the event type (e.g. 'keyup.toggle') + ## jQuery Compatibility Works with jQuery 1.4.2 and newer. It is known to be working with all the major browsers on all available platforms (Win/Mac/Linux) - * IE 6/7/8 - * FF 1.5/2/3 - * Opera-9 - * Safari-3 - * Chrome-0.2 + * IE 6/7/8+ + * FF 1.5/2/3+ + * Opera-9+ + * Safari-3+ + * Chrome-0.2+ ### Addendum diff --git a/test/SpecRunner.html b/test/SpecRunner.html index e5f5603..a875d32 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -1,28 +1,43 @@ - + - - Backbone Poller Unit Tests + + Jasmine Spec Runner - - - - - - + - + + + + + + + + + + + + + + + + + + + + + - + - - + + + - diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js index fc277bc..0c4f2a7 100644 --- a/test/spec/bindingSpec.js +++ b/test/spec/bindingSpec.js @@ -4,66 +4,117 @@ describe("binding functions to key combinations", function() { beforeEach(function() { - this.callbackFn = function(e) {}; + this.callbackFn = sinon.spy(); - spyOn(this, 'callbackFn'); this.fixture = $('
'); $('body').append(this.fixture); this.createInputEl = function(type, id) { - this.fixture.append(''); + var $el = $(''); + this.fixture.append($el); + return $el; }; this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel"]; // creates new key event - this.createKeyEvent = function(keycode, namespace) { + this.createKeyUpEvent = function(keyCode) { - var eventName = "keyup"; - if (namespace) { - eventName += '.' + namespace; - } - - var event = jQuery.Event(eventName); - event.keycode = keycode; - event.which = keycode; + var event = jQuery.Event('keyup'); + event.keyCode = keyCode; + event.which = keyCode; return event; }; }); afterEach(function(){ - $('#container').remove(); + this.fixture.remove(); $(document).unbind(); }); - it("should bind the 'return' hotkey event to the document and trigger the bound callback", function() { + it("should bind the 'return' key to the document and trigger the bound callback", function() { - this.createInputEl('text', '01'); $(document).bind('keyup', 'return', this.callbackFn); - $(document).trigger(this.createKeyEvent('13')); - expect(this.callbackFn).toHaveBeenCalled(); + + var event = this.createKeyUpEvent(13); + $(document).trigger(event); + sinon.assert.calledOnce(this.callbackFn); + }); - it("should bind 'Ctrl+a' and call the bound callback function", function() { + it("should bind the 'alt+s' keys and call the callback handler function", function() { + + $(document).bind('keyup', 'alt+a', this.callbackFn); + var event = this.createKeyUpEvent(65); + event.altKey = true; + $(document).trigger(event); + sinon.assert.calledOnce(this.callbackFn); + }); - this.createInputEl('text', '01'); - $(document).bind('keyup', 'Ctrl+a', this.callbackFn); + it("should bind the 'shift+pagedown' keys and call the callback handler function", function() { - var event = this.createKeyEvent('65'); - event.ctrlKey = true; + $(document).bind('keyup', 'shift+pagedown', this.callbackFn); + var event = this.createKeyUpEvent(34); + event.shiftKey = true; $(document).trigger(event); - expect(this.callbackFn).toHaveBeenCalled(); + sinon.assert.calledOnce(this.callbackFn); }); - it("should bind to 'Alt+a' and call the bound callback function", function() { + it("should bind the 'alt+shift+a' with a namespace, trigger the callback handler and unbind correctly", function() { + + var spy = sinon.spy(); - this.createInputEl('text', '01'); - $(document).bind('keyup', 'Alt+a', this.callbackFn); - var event = this.createKeyEvent('65'); + $(document).bind('keyup.a', 'alt+shift+a', spy); + $(document).bind('keyup.b', 'alt+shift+a', spy); + $(document).unbind('keyup.a'); // remove first binding, leaving only second + + var event = this.createKeyUpEvent(65); event.altKey = true; + event.shiftKey = true; $(document).trigger(event); - expect(this.callbackFn).toHaveBeenCalled(); + + // ensure only second binding is still in effect + sinon.assert.calledOnce(spy); + }); + + it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { + + var i = 0; + + _.each(this.text_input_types, function(input_type) { + var spy = sinon.spy(); + + var $el = this.createInputEl(input_type, ++i); + $(document).bind('keyup', 'a', spy); + + var event = this.createKeyUpEvent('65'); + $el.trigger(event); + + sinon.assert.notCalled(spy); + $(document).unbind(); + $el.remove(); + + }, this); + }); + + it("should bind and trigger events from an input element if bound directly", function() { + + var i = 0; + + _.each(this.text_input_types, function(input_type) { + var spy = sinon.spy(); + + var $el = this.createInputEl(input_type, ++i); + $el.bind('keyup', 'a', spy); + + var event = this.createKeyUpEvent('65'); + $el.trigger(event); + + sinon.assert.calledOnce(spy); + $el.remove(); + + }, this); }); }); From d0786524f6e43a1bfd65e31a4497560a52529eca Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Wed, 12 Feb 2014 00:34:55 +1100 Subject: [PATCH 26/59] Updated link in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e06b42..08335f9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Supported types are `'keydown'`, `'keyup'` and `'keypress'` ## Example -[Example] http://htmlpreview.github.com/?https://github.com/jeresig/jquery.hotkeys/master/test-static-05.html +[Example](http://htmlpreview.github.com/?https://github.com/jeresig/jquery.hotkeys/master/test-static-05.html) ## Notes From 3b37b7c9e683a915614515d46d8362f11dbe6249 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Mon, 24 Feb 2014 00:51:21 +1100 Subject: [PATCH 27/59] Reviewed 'meta' key functionality for Mac and added 'hyper' keyboard shortcut and other missing keys. --- README.md | 43 +++++++++++++++++++++-------- jquery.hotkeys.js | 44 ++++++++++++------------------ test-static-06.html | 59 ++++++++++++++++++++++++++++------------ test/spec/bindingSpec.js | 20 +++++++++++++- 4 files changed, 109 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 08335f9..fd1d6b4 100644 --- a/README.md +++ b/README.md @@ -29,23 +29,13 @@ Syntax when wanting to use jQuery's `on()`/`off` methods: this.value = this.value.replace('$', 'EUR'); }); -## Types - -Supported types are `'keydown'`, `'keyup'` and `'keypress'` - ## Example [Example](http://htmlpreview.github.com/?https://github.com/jeresig/jquery.hotkeys/master/test-static-05.html) -## Notes - -Modifiers are not case sensitive (Ctrl == ctrl == cTRL) - -If you want to use more than one modifiers (e.g. alt+ctrl+z) you should define them by an alphabetical order e.g. alt+ctrl+shift +## Event Types -Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing. - -You can use namespacing by adding a suffix to the event type (e.g. 'keyup.toggle') +Supported types are `'keydown'`, `'keyup'` and `'keypress'` ## jQuery Compatibility @@ -59,6 +49,35 @@ It is known to be working with all the major browsers on all available platforms * Safari-3+ * Chrome-0.2+ +## Notes + +Modifiers are not case sensitive (`Ctrl` == `ctrl` == `cTRL`) + +If you want to use more than one modifier (e.g. `alt+ctrl+z`) you should define them by an alphabetical order e.g. alt+ctrl+shift + +Hotkeys aren't tracked if you're inside of an input element (unless you explicitly bind the hotkey directly to the input). This helps to avoid conflict with normal user typing. + +You can use namespacing by adding a suffix to the event type (e.g. `keyup.toggle`) + + +### Meta and Hyper Keys +Meta and hyper keys don't register on `keyup` in any browser tested. + +#### Chrome 33.0.1750.117 +Meta key registers on `keydown` event. +Hyper key registers on `keydown` event. + +#### Firefox 27.0.1 and Safari 7.0.1 +Meta key registers on `keydown` and `keypress` events. +Hyper key registers on `keydown` and `keypress` events. + +#### Opera 19.0 +Meta key doesn't register at all :( +Hyper key registers on `keydown` and `keypress` events. + +#### TL;DR +Bind to `keydown` event for meta and hyper keys, but meta key does not work in Opera ;) + ### Addendum Firefox is the most liberal one in the manner of letting you capture all short-cuts even those that are built-in in the browser such as `Ctrl-t` for new tab, or `Ctrl-a` for selecting all text. You can always bubble them up to the browser by returning `true` in your handler. diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index a060fc0..d16d3b2 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -16,24 +16,24 @@ */ (function(jQuery){ - + jQuery.hotkeys = { version: "0.8", specialKeys: { 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", - 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", - 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/", - 220: "\\", 222: "'", 224: "meta" + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", + 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" }, - + shiftNums: { - "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", - "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", ".": ">", "/": "?", "\\": "|" } }; @@ -50,8 +50,9 @@ var origHandler = handleObj.handler, keys = handleObj.data.keys.toLowerCase().split(" "), - textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel"]; - + textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", + "time", "datetime", "datetime-local", "search", "color", "tel"]; + handleObj.handler = function( event ) { // Don't fire in text-accepting inputs that we didn't directly bind to if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || @@ -63,23 +64,14 @@ character = String.fromCharCode( event.which ).toLowerCase(), modif = "", possible = {}; - // check combinations (alt|ctrl|shift+anything) - if ( event.altKey && special !== "alt" ) { - modif += "alt+"; - } + jQuery.each([ "alt", "ctrl", "meta", "shift" ], function(index, specialKey) { + if (event[specialKey + 'Key'] && special !== specialKey) { + modif += specialKey + '+'; + } + }); - if ( event.ctrlKey && special !== "ctrl" ) { - modif += "ctrl+"; - } - - // TODO: Need to make sure this works consistently across platforms - if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { - modif += "meta+"; - } - if ( event.shiftKey && special !== "shift" ) { - modif += "shift+"; - } + modif = modif.replace('alt+ctrl+meta+shift', 'hyper'); if ( special ) { possible[ modif + special ] = true; diff --git a/test-static-06.html b/test-static-06.html index 5f18bc3..9befec3 100644 --- a/test-static-06.html +++ b/test-static-06.html @@ -10,31 +10,54 @@

KeyDown.

shiftKey:
ctrlKey:
altKey:
+ metaKey:

KeyPress.

currentTarget:
which:
shiftKey:
ctrlKey:
altKey:
+ metaKey:
+

KeyUp.

+ currentTarget:
+ which:
+ shiftKey:
+ ctrlKey:
+ altKey:
+ metaKey:
diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js index 0c4f2a7..e09b9f2 100644 --- a/test/spec/bindingSpec.js +++ b/test/spec/bindingSpec.js @@ -27,7 +27,7 @@ describe("binding functions to key combinations", function() { return event; }; - }); + }); afterEach(function(){ this.fixture.remove(); @@ -79,6 +79,24 @@ describe("binding functions to key combinations", function() { sinon.assert.calledOnce(spy); }); + it("should bind the 'meta+a' keys and call the callback handler function", function() { + + $(document).bind('keyup', 'meta+a', this.callbackFn); + var event = this.createKeyUpEvent(65); + event.metaKey = true; + $(document).trigger(event); + sinon.assert.calledOnce(this.callbackFn); + }); + + it("should bind the 'hyper+a' keys and call the callback handler function", function() { + + $(document).bind('keyup', 'hyper+a', this.callbackFn); + var event = this.createKeyUpEvent(65); + event.shiftKey = true, event.metaKey = true, event.altKey = true, event.ctrlKey = true; + $(document).trigger(event); + sinon.assert.calledOnce(this.callbackFn); + }); + it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { var i = 0; From f42f1178cbbf99abc6dc99772e6f3dc12198370d Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Fri, 21 Feb 2014 08:48:51 +1100 Subject: [PATCH 28/59] Reviewed 'meta' key functionality for Mac and added 'hyper' keyboard shortcut. --- jquery.hotkeys.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index d16d3b2..80e0364 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -21,20 +21,29 @@ version: "0.8", specialKeys: { - 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 8: "backspace", 9: "tab", 91: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", - 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/", + 220: "\\", 222: "'", 224: "meta" }, shiftNums: { "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", ".": ">", "/": "?", "\\": "|" + }, + + // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url + textAcceptingInputTypes: [ + "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", + "datetime-local", "search", "color", "tel"], + + options: { + filterTextInputs: true } }; @@ -49,17 +58,18 @@ } var origHandler = handleObj.handler, - keys = handleObj.data.keys.toLowerCase().split(" "), - textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", - "time", "datetime", "datetime-local", "search", "color", "tel"]; - - handleObj.handler = function( event ) { - // Don't fire in text-accepting inputs that we didn't directly bind to - if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || - jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) { + keys = handleObj.data.keys.toLowerCase().split(" "); + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + ( jQuery.hotkeys.options.filterTextInputs && + jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1 ) ) ) { return; } + var specialKeys = ['alt', 'ctrl', 'meta', 'shift']; + var special = jQuery.hotkeys.specialKeys[ event.keyCode ], character = String.fromCharCode( event.which ).toLowerCase(), modif = "", possible = {}; @@ -70,7 +80,6 @@ } }); - modif = modif.replace('alt+ctrl+meta+shift', 'hyper'); if ( special ) { From 111eeab1a366e486b297d9d0a0918e0df9ba69c4 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 1 Apr 2014 22:11:21 +1100 Subject: [PATCH 29/59] Added ability to override hotkey ignoring on text input field types. --- Gruntfile.js | 4 +- jquery.hotkeys.js | 19 +++--- test/SpecRunner.html | 4 +- test/spec/bindingSpec.js | 129 +++++++++++++++++++++------------------ 4 files changed, 80 insertions(+), 76 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index f4ee345..28f1c3b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,14 +7,12 @@ module.exports = function(grunt) { "undef": true, "unused": true }, - files: 'jquery.hotkeys.js' }, jasmine: { pivotal: { - src: 'jquery.hotkeys.js', options: { - vendor: ['jquery-1.4.2.js', 'test/lib/**.js'], + vendor: ['jquery-1.4.2.js', 'jquery.hotkeys.js', 'test/lib/**.js'], outfile: 'test/SpecRunner.html', keepRunner: true, specs: 'test/spec/*Spec.js' diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 80e0364..5d4f038 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -21,14 +21,14 @@ version: "0.8", specialKeys: { - 8: "backspace", 9: "tab", 91: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/", - 220: "\\", 222: "'", 224: "meta" + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", + 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" }, shiftNums: { @@ -47,10 +47,10 @@ } }; - function keyHandler( handleObj ) { - if ( typeof handleObj.data === "string" ) { - handleObj.data = { keys: handleObj.data }; - } + function keyHandler( handleObj ) { + if ( typeof handleObj.data === "string" ) { + handleObj.data = { keys: handleObj.data }; + } // Only care when a possible input has been specified if ( !handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { @@ -68,8 +68,6 @@ return; } - var specialKeys = ['alt', 'ctrl', 'meta', 'shift']; - var special = jQuery.hotkeys.specialKeys[ event.keyCode ], character = String.fromCharCode( event.which ).toLowerCase(), modif = "", possible = {}; @@ -80,6 +78,7 @@ } }); + modif = modif.replace('alt+ctrl+meta+shift', 'hyper'); if ( special ) { diff --git a/test/SpecRunner.html b/test/SpecRunner.html index a875d32..f1b0583 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -18,6 +18,8 @@ + + @@ -30,8 +32,6 @@ - - diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js index e09b9f2..50cf923 100644 --- a/test/spec/bindingSpec.js +++ b/test/spec/bindingSpec.js @@ -4,6 +4,55 @@ describe("binding functions to key combinations", function() { beforeEach(function() { + jQuery.hotkeys.options.filterTextInputs = true; + + this.cleanup = function($el) { + $el.remove(); + $(document).unbind(); + }; + + this.bindAndTriggerKeyEvent = function(keyAsText, keyCode, callback, keyModifiers) { + + var keyEvent = 'keyup'; + + keyModifiers = _.extend( {ctrl: false, alt: false, shift: false, meta: false}, keyModifiers); + + $(document).bind(keyEvent, keyAsText, callback); + var event = this.createKeyEvent(keyCode, keyEvent); + + event.altKey = !!keyModifiers.alt; + event.ctrlKey = !!keyModifiers.ctrl; + event.shiftKey = !!keyModifiers['shift']; + event.metaKey = !!keyModifiers.meta; + + return $(document).trigger(event); + }; + + this.assertTextInputKeyEventTriggered = function (bindDirectly, expectToBeTriggered) { + + var keyEvent = 'keyup'; + var keyCode = '65'; + var keyAsText = 'a'; + + var i = 0; + + _.each(this.textInputTypes, function(input_type) { + var spy = sinon.spy(); + + var $el = this.createInputEl(input_type, ++i); + var $bindElement = bindDirectly ? $el : $(document); + + $bindElement.bind(keyEvent, keyAsText, spy); + + var event = this.createKeyEvent(keyCode); + $el.trigger(event); + + expectToBeTriggered ? sinon.assert.calledOnce(spy) : sinon.assert.notCalled(spy); + this.cleanup($el); + + }, this); + }; + this.callbackFn = sinon.spy(); this.fixture = $('
'); @@ -15,13 +64,13 @@ describe("binding functions to key combinations", function() { return $el; }; - this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", - "time", "datetime", "datetime-local", "search", "color", "tel"]; + this.textInputTypes = jQuery.hotkeys.textAcceptingInputTypes; // creates new key event - this.createKeyUpEvent = function(keyCode) { + this.createKeyEvent = function(keyCode, keyEvent) { + keyEvent = keyEvent || 'keyup'; - var event = jQuery.Event('keyup'); + var event = jQuery.Event(keyEvent); event.keyCode = keyCode; event.which = keyCode; @@ -30,35 +79,24 @@ describe("binding functions to key combinations", function() { }); afterEach(function(){ - this.fixture.remove(); - $(document).unbind(); + this.cleanup(this.fixture); }); it("should bind the 'return' key to the document and trigger the bound callback", function() { - $(document).bind('keyup', 'return', this.callbackFn); - - var event = this.createKeyUpEvent(13); - $(document).trigger(event); + this.bindAndTriggerKeyEvent('return', '13', this.callbackFn); sinon.assert.calledOnce(this.callbackFn); - }); it("should bind the 'alt+s' keys and call the callback handler function", function() { - $(document).bind('keyup', 'alt+a', this.callbackFn); - var event = this.createKeyUpEvent(65); - event.altKey = true; - $(document).trigger(event); + this.bindAndTriggerKeyEvent('alt+a', '65', this.callbackFn, {alt: true}); sinon.assert.calledOnce(this.callbackFn); }); it("should bind the 'shift+pagedown' keys and call the callback handler function", function() { - $(document).bind('keyup', 'shift+pagedown', this.callbackFn); - var event = this.createKeyUpEvent(34); - event.shiftKey = true; - $(document).trigger(event); + this.bindAndTriggerKeyEvent('shift+pagedown', '34', this.callbackFn, {shift: true}); sinon.assert.calledOnce(this.callbackFn); }); @@ -70,10 +108,7 @@ describe("binding functions to key combinations", function() { $(document).bind('keyup.b', 'alt+shift+a', spy); $(document).unbind('keyup.a'); // remove first binding, leaving only second - var event = this.createKeyUpEvent(65); - event.altKey = true; - event.shiftKey = true; - $(document).trigger(event); + this.bindAndTriggerKeyEvent('alt+shift+a', '65', this.callbackFn, {shift: true, alt: true}); // ensure only second binding is still in effect sinon.assert.calledOnce(spy); @@ -81,58 +116,30 @@ describe("binding functions to key combinations", function() { it("should bind the 'meta+a' keys and call the callback handler function", function() { - $(document).bind('keyup', 'meta+a', this.callbackFn); - var event = this.createKeyUpEvent(65); - event.metaKey = true; - $(document).trigger(event); + this.bindAndTriggerKeyEvent('meta+a', '65', this.callbackFn, {meta: true}); sinon.assert.calledOnce(this.callbackFn); }); it("should bind the 'hyper+a' keys and call the callback handler function", function() { - $(document).bind('keyup', 'hyper+a', this.callbackFn); - var event = this.createKeyUpEvent(65); - event.shiftKey = true, event.metaKey = true, event.altKey = true, event.ctrlKey = true; - $(document).trigger(event); + this.bindAndTriggerKeyEvent('hyper+a', '65', this.callbackFn, {meta: true, shift: true, alt: true, ctrl: true}); sinon.assert.calledOnce(this.callbackFn); }); it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { - var i = 0; - - _.each(this.text_input_types, function(input_type) { - var spy = sinon.spy(); - - var $el = this.createInputEl(input_type, ++i); - $(document).bind('keyup', 'a', spy); - - var event = this.createKeyUpEvent('65'); - $el.trigger(event); - - sinon.assert.notCalled(spy); - $(document).unbind(); - $el.remove(); - - }, this); + this.assertTextInputKeyEventTriggered(false, false); }); - it("should bind and trigger events from an input element if bound directly", function() { - - var i = 0; - - _.each(this.text_input_types, function(input_type) { - var spy = sinon.spy(); + it("should bind and trigger events from a text input element if bound directly", function() { - var $el = this.createInputEl(input_type, ++i); - $el.bind('keyup', 'a', spy); - - var event = this.createKeyUpEvent('65'); - $el.trigger(event); + this.assertTextInputKeyEventTriggered(true, true); + }); - sinon.assert.calledOnce(spy); - $el.remove(); + it("should trigger event handler callbacks bound to any text input types if filter is turned off", function() { - }, this); + jQuery.hotkeys.options.filterTextInputs = false; + this.assertTextInputKeyEventTriggered(false, true); }); + }); From c6d85847db70a11dc8fde3d51ff0ed4b7d9025b8 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 5 Jun 2014 10:44:17 +0200 Subject: [PATCH 30/59] Added syntax highlighting --- README.md | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fd1d6b4..6b5ba45 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,31 @@ This plugin is based off of the plugin by Tzury Bar Yochay: [jQuery.hotkeys](htt The syntax is as follows: - $(expression).bind(types, keys, handler); - $(expression).unbind(types, handler); - - $(document).bind('keydown', 'ctrl+a', fn); - - // e.g. replace '$' sign with 'EUR' - $('input.foo').bind('keyup', '$', function(){ - this.value = this.value.replace('$', 'EUR'); - }); - +```javascript +$(expression).bind(types, keys, handler); +$(expression).unbind(types, handler); + +$(document).bind('keydown', 'ctrl+a', fn); + +// e.g. replace '$' sign with 'EUR' +$('input.foo').bind('keyup', '$', function(){ + this.value = this.value.replace('$', 'EUR'); +}); +``` + Syntax when wanting to use jQuery's `on()`/`off` methods: - $(expression).on(types, null, keys, handler); - $(expression).off(types, handler); - - $(document).on('keydown', null, 'ctrl+a', fn); - - // e.g. replace '$' sign with 'EUR' - $('input.foo').on('keyup', null, '$', function(){ - this.value = this.value.replace('$', 'EUR'); - }); +```javascript +$(expression).on(types, null, keys, handler); +$(expression).off(types, handler); + +$(document).on('keydown', null, 'ctrl+a', fn); + +// e.g. replace '$' sign with 'EUR' +$('input.foo').on('keyup', null, '$', function(){ + this.value = this.value.replace('$', 'EUR'); +}); +``` ## Example From 7f6a925cabde8da9060bbc2c3ea37a8f635a1709 Mon Sep 17 00:00:00 2001 From: paladox2015 Date: Fri, 18 Jul 2014 11:15:05 +0100 Subject: [PATCH 31/59] Update jquery.hotkeys.js Updating link other link did not work. --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 5d4f038..015f2a1 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -4,7 +4,7 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * * Based upon the plugin by Tzury Bar Yochay: - * http://github.com/tzuryby/hotkeys + * https://github.com/tzuryby/jquery.hotkeys * * Original idea by: * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ From cf24c91990f5d9392e0bc3175c33eba36d95f448 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 12 Aug 2014 09:01:21 +1000 Subject: [PATCH 32/59] Adding grunt-jsbeautify. --- Gruntfile.js | 34 +++++++++++++++++++++++++++++++--- package.json | 3 ++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 28f1c3b..c4ba9d9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,6 +2,31 @@ module.exports = function(grunt) { // Configuration. grunt.initConfig({ + // verifies we have formatted our js and HTML according to our style conventions + jsbeautifier: { + files: ['Gruntfile.js', 'jquery.hotkeys.js', 'test/spec/**/*.js'], + options: { + mode: 'VERIFY_ONLY', + js: { + 'indent_size': 2, + 'indent_char': ' ', + 'indent_level': 0, + 'indent_with_tabs': false, + 'preserve_newlines': true, + 'max_preserve_newlines': 2, + 'jslint_happy': false, + 'brace_style': 'end-expand', + 'indent_scripts': 'keep', + 'keep_array_indentation': true, + 'keep_function_indentation': false, + 'space_before_conditional': true, + 'break_chained_methods': false, + 'eval_code': false, + 'unescape_strings': false, + 'wrap_line_length': 0 + } + } + }, jshint: { options: { "undef": true, @@ -34,12 +59,15 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-watch'); - // Task loading. + // Beautify javascript + grunt.loadNpmTasks("grunt-jsbeautifier"); + + // Task loading grunt.loadNpmTasks("grunt-contrib-jshint"); - // tests + // Running tests grunt.loadNpmTasks('grunt-contrib-jasmine'); // Task registration. - grunt.registerTask("default", ["jshint", "jasmine"]); + grunt.registerTask("default", ["jsbeautifier", "jshint", "jasmine"]); }; diff --git a/package.json b/package.json index 1c95b62..c3d57fd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-jshint": "~0.8.0", - "grunt-contrib-jasmine": "~0.6.0" + "grunt-contrib-jasmine": "~0.6.0", + "grunt-jsbeautifier": "~0.2.7" }, "repository": { "type": "git", From 58ee2b7a17a32467c4e19634d0c8c4beafc62595 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 12 Aug 2014 11:18:40 +1000 Subject: [PATCH 33/59] Re-formatting files using js-beautify. --- Gruntfile.js | 2 +- jquery.hotkeys.js | 220 ++++++++++++++++++++++++++------------- test/spec/bindingSpec.js | 37 +++++-- 3 files changed, 177 insertions(+), 82 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index c4ba9d9..ed158eb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,7 @@ module.exports = function(grunt) { 'max_preserve_newlines': 2, 'jslint_happy': false, 'brace_style': 'end-expand', - 'indent_scripts': 'keep', + 'indent_scripts ': 'keep', 'keep_array_indentation': true, 'keep_function_indentation': false, 'space_before_conditional': true, diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 5d4f038..e4f57d2 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -8,33 +8,105 @@ * * Original idea by: * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ -*/ + */ /* * One small change is: now keys are passed by object { keys: '...' } * Might be useful, when you want to pass some other data to your handler */ -(function(jQuery){ - - jQuery.hotkeys = { - version: "0.8", - - specialKeys: { - 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", - 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=", - 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", - 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", - 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=", - 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'" - }, - - shiftNums: { - "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", - "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", - ".": ">", "/": "?", "\\": "|" +(function(jQuery) { + + jQuery.hotkeys = { + version: "0.8", + + specialKeys: { + 8: "backspace", + 9: "tab", + 10: "return", + 13: "return", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause", + 20: "capslock", + 27: "esc", + 32: "space", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "insert", + 46: "del", + 59: ";", + 61: "=", + 96: "0", + 97: "1", + 98: "2", + 99: "3", + 100: "4", + 101: "5", + 102: "6", + 103: "7", + 104: "8", + 105: "9", + 106: "*", + 107: "+", + 109: "-", + 110: ".", + 111: "/", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 144: "numlock", + 145: "scroll", + 173: "-", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "'" + }, + + shiftNums: { + "`": "~", + "1": "!", + "2": "@", + "3": "#", + "4": "$", + "5": "%", + "6": "^", + "7": "&", + "8": "*", + "9": "(", + "0": ")", + "-": "_", + "=": "+", + ";": ": ", + "'": "\"", + ",": "<", + ".": ">", + "/": "?", + "\\": "|" }, // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url @@ -44,67 +116,71 @@ options: { filterTextInputs: true - } - }; + } + }; - function keyHandler( handleObj ) { - if ( typeof handleObj.data === "string" ) { - handleObj.data = { keys: handleObj.data }; + function keyHandler(handleObj) { + if (typeof handleObj.data === "string") { + handleObj.data = { + keys: handleObj.data + }; } - // Only care when a possible input has been specified - if ( !handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) { - return; - } + // Only care when a possible input has been specified + if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") { + return; + } - var origHandler = handleObj.handler, - keys = handleObj.data.keys.toLowerCase().split(" "); + var origHandler = handleObj.handler, + keys = handleObj.data.keys.toLowerCase().split(" "); - handleObj.handler = function( event ) { + handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to - if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || - ( jQuery.hotkeys.options.filterTextInputs && - jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1 ) ) ) { - return; - } - - var special = jQuery.hotkeys.specialKeys[ event.keyCode ], - character = String.fromCharCode( event.which ).toLowerCase(), - modif = "", possible = {}; - - jQuery.each([ "alt", "ctrl", "meta", "shift" ], function(index, specialKey) { + if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || + (jQuery.hotkeys.options.filterTextInputs && + jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { + return; + } + + var special = jQuery.hotkeys.specialKeys[event.keyCode], + character = String.fromCharCode(event.which).toLowerCase(), + modif = "", + possible = {}; + + jQuery.each(["alt", "ctrl", "meta", "shift"], function(index, specialKey) { if (event[specialKey + 'Key'] && special !== specialKey) { modif += specialKey + '+'; } }); - modif = modif.replace('alt+ctrl+meta+shift', 'hyper'); - if ( special ) { - possible[ modif + special ] = true; - } - - if ( character ) { - possible[ modif + character ] = true; - possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; - - // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" - if ( modif === "shift+" ) { - possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; - } - } - - for ( var i = 0, l = keys.length; i < l; i++ ) { - if ( possible[ keys[i] ] ) { - return origHandler.apply( this, arguments ); - } - } - }; - } - - jQuery.each([ "keydown", "keyup", "keypress" ], function() { - jQuery.event.special[ this ] = { add: keyHandler }; - }); - -})( this.jQuery ); + if (special) { + possible[modif + special] = true; + } + + if (character) { + possible[modif + character] = true; + possible[modif + jQuery.hotkeys.shiftNums[character]] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if (modif === "shift+") { + possible[jQuery.hotkeys.shiftNums[character]] = true; + } + } + + for (var i = 0, l = keys.length; i < l; i++) { + if (possible[keys[i]]) { + return origHandler.apply(this, arguments); + } + } + }; + } + + jQuery.each(["keydown", "keyup", "keypress"], function() { + jQuery.event.special[this] = { + add: keyHandler + }; + }); + +})(this.jQuery); diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js index 50cf923..9e1832f 100644 --- a/test/spec/bindingSpec.js +++ b/test/spec/bindingSpec.js @@ -15,7 +15,12 @@ describe("binding functions to key combinations", function() { var keyEvent = 'keyup'; - keyModifiers = _.extend( {ctrl: false, alt: false, shift: false, meta: false}, keyModifiers); + keyModifiers = _.extend({ + ctrl: false, + alt: false, + shift: false, + meta: false + }, keyModifiers); $(document).bind(keyEvent, keyAsText, callback); var event = this.createKeyEvent(keyCode, keyEvent); @@ -28,7 +33,7 @@ describe("binding functions to key combinations", function() { return $(document).trigger(event); }; - this.assertTextInputKeyEventTriggered = function (bindDirectly, expectToBeTriggered) { + this.assertTextInputKeyEventTriggered = function(bindDirectly, expectToBeTriggered) { var keyEvent = 'keyup'; var keyCode = '65'; @@ -76,9 +81,9 @@ describe("binding functions to key combinations", function() { return event; }; - }); + }); - afterEach(function(){ + afterEach(function() { this.cleanup(this.fixture); }); @@ -90,13 +95,17 @@ describe("binding functions to key combinations", function() { it("should bind the 'alt+s' keys and call the callback handler function", function() { - this.bindAndTriggerKeyEvent('alt+a', '65', this.callbackFn, {alt: true}); + this.bindAndTriggerKeyEvent('alt+a', '65', this.callbackFn, { + alt: true + }); sinon.assert.calledOnce(this.callbackFn); }); it("should bind the 'shift+pagedown' keys and call the callback handler function", function() { - this.bindAndTriggerKeyEvent('shift+pagedown', '34', this.callbackFn, {shift: true}); + this.bindAndTriggerKeyEvent('shift+pagedown', '34', this.callbackFn, { + shift: true + }); sinon.assert.calledOnce(this.callbackFn); }); @@ -108,7 +117,10 @@ describe("binding functions to key combinations", function() { $(document).bind('keyup.b', 'alt+shift+a', spy); $(document).unbind('keyup.a'); // remove first binding, leaving only second - this.bindAndTriggerKeyEvent('alt+shift+a', '65', this.callbackFn, {shift: true, alt: true}); + this.bindAndTriggerKeyEvent('alt+shift+a', '65', this.callbackFn, { + shift: true, + alt: true + }); // ensure only second binding is still in effect sinon.assert.calledOnce(spy); @@ -116,13 +128,20 @@ describe("binding functions to key combinations", function() { it("should bind the 'meta+a' keys and call the callback handler function", function() { - this.bindAndTriggerKeyEvent('meta+a', '65', this.callbackFn, {meta: true}); + this.bindAndTriggerKeyEvent('meta+a', '65', this.callbackFn, { + meta: true + }); sinon.assert.calledOnce(this.callbackFn); }); it("should bind the 'hyper+a' keys and call the callback handler function", function() { - this.bindAndTriggerKeyEvent('hyper+a', '65', this.callbackFn, {meta: true, shift: true, alt: true, ctrl: true}); + this.bindAndTriggerKeyEvent('hyper+a', '65', this.callbackFn, { + meta: true, + shift: true, + alt: true, + ctrl: true + }); sinon.assert.calledOnce(this.callbackFn); }); From 26c02d0ecc44a4eebb627db9fd1b4af36e75d503 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 19 Aug 2014 12:31:08 +1000 Subject: [PATCH 34/59] Fixing keypress binding issue introduced in 8b73fdc and 3cae520. --- jquery.hotkeys.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index e4f57d2..4c45486 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -135,14 +135,15 @@ keys = handleObj.data.keys.toLowerCase().split(" "); handleObj.handler = function(event) { - // Don't fire in text-accepting inputs that we didn't directly bind to + // Don't fire in text-accepting inputs that we didn't directly bind to if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || (jQuery.hotkeys.options.filterTextInputs && jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { return; } - var special = jQuery.hotkeys.specialKeys[event.keyCode], + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which], + character = String.fromCharCode(event.which).toLowerCase(), modif = "", possible = {}; @@ -158,8 +159,7 @@ if (special) { possible[modif + special] = true; } - - if (character) { + else { possible[modif + character] = true; possible[modif + jQuery.hotkeys.shiftNums[character]] = true; From 8f3d041a5c5ab3ba5bdc4894729c75213390bb3e Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 19 Aug 2014 12:34:03 +1000 Subject: [PATCH 35/59] Updated key binding specs and added namespaced unbind specs. --- test/SpecRunner.html | 2 +- test/spec/bindingSpec.js | 164 ------------------------------------ test/spec/keyBindingSpec.js | 138 ++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 165 deletions(-) delete mode 100644 test/spec/bindingSpec.js create mode 100644 test/spec/keyBindingSpec.js diff --git a/test/SpecRunner.html b/test/SpecRunner.html index f1b0583..19faa49 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -32,7 +32,7 @@ - + diff --git a/test/spec/bindingSpec.js b/test/spec/bindingSpec.js deleted file mode 100644 index 9e1832f..0000000 --- a/test/spec/bindingSpec.js +++ /dev/null @@ -1,164 +0,0 @@ -// use http://jonathan.tang.name/files/js_keycode/test_keycode.html -// to discover key codes -describe("binding functions to key combinations", function() { - - beforeEach(function() { - - jQuery.hotkeys.options.filterTextInputs = true; - - this.cleanup = function($el) { - $el.remove(); - $(document).unbind(); - }; - - this.bindAndTriggerKeyEvent = function(keyAsText, keyCode, callback, keyModifiers) { - - var keyEvent = 'keyup'; - - keyModifiers = _.extend({ - ctrl: false, - alt: false, - shift: false, - meta: false - }, keyModifiers); - - $(document).bind(keyEvent, keyAsText, callback); - var event = this.createKeyEvent(keyCode, keyEvent); - - event.altKey = !!keyModifiers.alt; - event.ctrlKey = !!keyModifiers.ctrl; - event.shiftKey = !!keyModifiers['shift']; - event.metaKey = !!keyModifiers.meta; - - return $(document).trigger(event); - }; - - this.assertTextInputKeyEventTriggered = function(bindDirectly, expectToBeTriggered) { - - var keyEvent = 'keyup'; - var keyCode = '65'; - var keyAsText = 'a'; - - var i = 0; - - _.each(this.textInputTypes, function(input_type) { - var spy = sinon.spy(); - - var $el = this.createInputEl(input_type, ++i); - var $bindElement = bindDirectly ? $el : $(document); - - $bindElement.bind(keyEvent, keyAsText, spy); - - var event = this.createKeyEvent(keyCode); - $el.trigger(event); - - expectToBeTriggered ? sinon.assert.calledOnce(spy) : sinon.assert.notCalled(spy); - this.cleanup($el); - - }, this); - }; - - this.callbackFn = sinon.spy(); - - this.fixture = $('
'); - $('body').append(this.fixture); - - this.createInputEl = function(type, id) { - var $el = $(''); - this.fixture.append($el); - return $el; - }; - - this.textInputTypes = jQuery.hotkeys.textAcceptingInputTypes; - - // creates new key event - this.createKeyEvent = function(keyCode, keyEvent) { - keyEvent = keyEvent || 'keyup'; - - var event = jQuery.Event(keyEvent); - event.keyCode = keyCode; - event.which = keyCode; - - return event; - }; - }); - - afterEach(function() { - this.cleanup(this.fixture); - }); - - it("should bind the 'return' key to the document and trigger the bound callback", function() { - - this.bindAndTriggerKeyEvent('return', '13', this.callbackFn); - sinon.assert.calledOnce(this.callbackFn); - }); - - it("should bind the 'alt+s' keys and call the callback handler function", function() { - - this.bindAndTriggerKeyEvent('alt+a', '65', this.callbackFn, { - alt: true - }); - sinon.assert.calledOnce(this.callbackFn); - }); - - it("should bind the 'shift+pagedown' keys and call the callback handler function", function() { - - this.bindAndTriggerKeyEvent('shift+pagedown', '34', this.callbackFn, { - shift: true - }); - sinon.assert.calledOnce(this.callbackFn); - }); - - it("should bind the 'alt+shift+a' with a namespace, trigger the callback handler and unbind correctly", function() { - - var spy = sinon.spy(); - - $(document).bind('keyup.a', 'alt+shift+a', spy); - $(document).bind('keyup.b', 'alt+shift+a', spy); - $(document).unbind('keyup.a'); // remove first binding, leaving only second - - this.bindAndTriggerKeyEvent('alt+shift+a', '65', this.callbackFn, { - shift: true, - alt: true - }); - - // ensure only second binding is still in effect - sinon.assert.calledOnce(spy); - }); - - it("should bind the 'meta+a' keys and call the callback handler function", function() { - - this.bindAndTriggerKeyEvent('meta+a', '65', this.callbackFn, { - meta: true - }); - sinon.assert.calledOnce(this.callbackFn); - }); - - it("should bind the 'hyper+a' keys and call the callback handler function", function() { - - this.bindAndTriggerKeyEvent('hyper+a', '65', this.callbackFn, { - meta: true, - shift: true, - alt: true, - ctrl: true - }); - sinon.assert.calledOnce(this.callbackFn); - }); - - it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { - - this.assertTextInputKeyEventTriggered(false, false); - }); - - it("should bind and trigger events from a text input element if bound directly", function() { - - this.assertTextInputKeyEventTriggered(true, true); - }); - - it("should trigger event handler callbacks bound to any text input types if filter is turned off", function() { - - jQuery.hotkeys.options.filterTextInputs = false; - this.assertTextInputKeyEventTriggered(false, true); - }); - -}); diff --git a/test/spec/keyBindingSpec.js b/test/spec/keyBindingSpec.js new file mode 100644 index 0000000..2b08b46 --- /dev/null +++ b/test/spec/keyBindingSpec.js @@ -0,0 +1,138 @@ +// use http://jonathan.tang.name/files/js_keycode/test_keycode.html +// to discover key codes + +// NOTE: keypress events will only test correctly on single keypresses (or in combination with a shift key) +describe("binding functions to key combinations", function() { + + beforeEach(function() { + + this.callbackFn = sinon.spy(); + + this.fixture = $('
'); + $('body').append(this.fixture); + + this.createInputEl = function(type, id) { + var $el = $(''); + this.fixture.append($el); + return $el; + }; + + this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", + "time", "datetime", "datetime-local", "search", "color", "tel"]; + + // creates new key event + this.createKeyEvent = function(keyCode, keyEventType) { + + keyEventType = keyEventType || 'keyup'; + + var event = jQuery.Event(keyEventType); + event.keyCode = keyCode; + event.which = keyCode; + + return event; + }; + + this.assertHotKeyBinding = function(keyEvent, keyCombinationAsText, keyCombinationAsKeyCode, modifiers, $el) { + + if (!keyEvent || !keyCombinationAsText || !keyCombinationAsKeyCode) { + throw new Error("Missing arguments for assertion, check your arguments."); + } + + modifiers = modifiers || []; + $el = $el || $(document); + + var spy = sinon.spy(); + + $el.bind(keyEvent, keyCombinationAsText, spy); + + var event = this.createKeyEvent(keyCombinationAsKeyCode, keyEvent); + + $.each(modifiers, function(index, modifier) { + event[modifier + 'Key'] = true; + }); + + $el.trigger(event); + sinon.assert.calledOnce(spy); + } + }); + + afterEach(function() { + this.fixture.remove(); + $(document).unbind(); + }); + + it("should bind the 'return' key to the document and trigger the bound callback", function() { + this.assertHotKeyBinding('keyup', 'return', 13); + }); + + it("should bind the 'alt+s' keys and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'alt+a', 65, ['alt']); + }); + + it("should bind the 'alt+f2' keys for keyup and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'alt+f2', 113, ['alt']); + }); + + it("should bind the 'shift+pagedown' keys and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'shift+pagedown', 34, ['shift']); + }); + + it("should bind the 'alt+shift+a' with a namespace, trigger the callback handler and unbind correctly", function() { + + var spy = sinon.spy(); + + $(document).bind('keyup.a', 'alt+shift+a', spy); + $(document).bind('keyup.b', 'alt+shift+a', spy); + $(document).unbind('keyup.a'); // remove first binding, leaving only second + + var event = this.createKeyEvent(65, 'keyup'); + event.altKey = true; + event.shiftKey = true; + $(document).trigger(event); + + // ensure only second binding is still in effect + sinon.assert.calledOnce(spy); + }); + + it("should bind the 'meta+a' keys and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'meta+a', 65, ['meta']); + }); + + it("should bind the 'hyper+a' keys and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'hyper+a', 65, ['alt', 'ctrl', 'meta', 'shift']); + }); + + it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { + + var i = 0; + + _.each(this.text_input_types, function(input_type) { + + var spy = sinon.spy(); + + var $el = this.createInputEl(input_type, ++i); + $(document).bind('keyup', 'a', spy); + + var event = this.createKeyEvent('65', 'keyup'); + $el.trigger(event); + + sinon.assert.notCalled(spy); + $(document).unbind(); + $el.remove(); + + }, this); + }); + + it("should bind and trigger events from an input element if bound directly", function() { + + var i = 0; + + _.each(this.text_input_types, function(input_type) { + + var $el = this.createInputEl(input_type, ++i); + this.assertHotKeyBinding('keyup', 'a', 65, [], $el); + $el.remove(); // unbound when removed + + }, this); + }); +}); From a282dc302a22319382f3e41612f4c9b06f04af82 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 19 Aug 2014 12:34:55 +1000 Subject: [PATCH 36/59] Updated README.md example link to use rawgit.com (previous link not loading libraries correctly). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd1d6b4..36b5006 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Syntax when wanting to use jQuery's `on()`/`off` methods: ## Example -[Example](http://htmlpreview.github.com/?https://github.com/jeresig/jquery.hotkeys/master/test-static-05.html) +[Example](https://cdn.rawgit.com/jeresig/jquery.hotkeys/master/test-static-01.html) ## Event Types From 7bff0f9e2c3595dc610a17f052668c24a68ee3c6 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 19 Aug 2014 16:28:56 +1000 Subject: [PATCH 37/59] Fixing case where metaKey is triggered when ctrlKey pressed. --- jquery.hotkeys.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 4c45486..18a80dd 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -143,18 +143,25 @@ } var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which], - character = String.fromCharCode(event.which).toLowerCase(), modif = "", possible = {}; - jQuery.each(["alt", "ctrl", "meta", "shift"], function(index, specialKey) { + jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) { + if (event[specialKey + 'Key'] && special !== specialKey) { modif += specialKey + '+'; } }); - modif = modif.replace('alt+ctrl+meta+shift', 'hyper'); + // metaKey is triggered off ctrlKey erronously + if (event.metaKey && !event.ctrlKey && special !== "meta") { + modif += "meta+"; + } + + if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) { + modif = modif.replace("alt+ctrl+shift+", "hyper+"); + } if (special) { possible[modif + special] = true; From 950ece85b4c5b18451a809b1f4b79720e05f8378 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 19 Aug 2014 16:29:05 +1000 Subject: [PATCH 38/59] Added test case for ctrl key binding. --- test/spec/keyBindingSpec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/spec/keyBindingSpec.js b/test/spec/keyBindingSpec.js index 2b08b46..a2d6309 100644 --- a/test/spec/keyBindingSpec.js +++ b/test/spec/keyBindingSpec.js @@ -68,6 +68,9 @@ describe("binding functions to key combinations", function() { it("should bind the 'alt+s' keys and call the callback handler function", function() { this.assertHotKeyBinding('keyup', 'alt+a', 65, ['alt']); }); + it("should bind the 'ctrl+s' keys and call the callback handler function", function() { + this.assertHotKeyBinding('keyup', 'ctrl+a', 65, ['ctrl']); + }); it("should bind the 'alt+f2' keys for keyup and call the callback handler function", function() { this.assertHotKeyBinding('keyup', 'alt+f2', 113, ['alt']); From f011f8c03dbdad26d14ee9c88a035999386388ff Mon Sep 17 00:00:00 2001 From: Larry Davis Date: Wed, 20 Aug 2014 10:32:16 -0700 Subject: [PATCH 39/59] Update dependencies, add missing grunt-contrib-watch dependency * Use caret instead of ~ for safer dependency management * This drops support for running the build on Node 0.8 * Update SpecRunner.html to have new links for ES5 shim * This happens automatically when you run tests with grunt-contrib-jasmine@0.7.0 --- package.json | 9 +++++---- test/SpecRunner.html | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c3d57fd..eefbba8 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,11 @@ "description": "**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.", "main": "jquery.hotkeys.js", "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.8.0", - "grunt-contrib-jasmine": "~0.6.0", - "grunt-jsbeautifier": "~0.2.7" + "grunt": "^0.4.5", + "grunt-contrib-jasmine": "^0.7.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-jsbeautifier": "^0.2.7" }, "repository": { "type": "git", diff --git a/test/SpecRunner.html b/test/SpecRunner.html index 19faa49..c1d7248 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -3,12 +3,16 @@ Jasmine Spec Runner + + + + - + @@ -37,7 +41,5 @@ - - From 5183bff8ae0bc545e8dc0e897b534e9e231244fa Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Wed, 20 Aug 2014 17:46:28 -0400 Subject: [PATCH 40/59] make this module check for jQuery on window if it's not in this --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 18a80dd..6934200 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -190,4 +190,4 @@ }; }); -})(this.jQuery); +})(this.jQuery || window.jQuery); From 035bdd8dc315f675a881d9e4b2f9184e0443a6f6 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 26 Aug 2014 17:28:52 -0400 Subject: [PATCH 41/59] hint to jshint that this module is for the browser --- jquery.hotkeys.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 6934200..36565fb 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -1,3 +1,5 @@ +/*jslint browser: true*/ + /* * jQuery Hotkeys Plugin * Copyright 2010, John Resig From d8d4644869ab52b4bd2a1f9eb54174a3f8e8ed57 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 26 Aug 2014 20:38:43 -0400 Subject: [PATCH 42/59] first check for global reference to jQuery, for Browserified jQuery compatibility --- jquery.hotkeys.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 36565fb..1822ecd 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -1,4 +1,5 @@ /*jslint browser: true*/ +/*jslint jquery: true*/ /* * jQuery Hotkeys Plugin @@ -192,4 +193,4 @@ }; }); -})(this.jQuery || window.jQuery); +})(jQuery || this.jQuery || window.jQuery); From 72830973ccaea210546fdb150f2e677889b37125 Mon Sep 17 00:00:00 2001 From: Morgante Pell Date: Tue, 26 Aug 2014 20:53:17 -0400 Subject: [PATCH 43/59] include brief remarks on browserify in README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 46807f8..6511f24 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,21 @@ It is known to be working with all the major browsers on all available platforms * Safari-3+ * Chrome-0.2+ +## Browserify Compatibility +If you want to include this module in a Browserified project, just add it to node_modules and require it. +```javascript +require('jquery.javascript'); +``` + +This will work if jQuery is global (ex. served from a CDN). If it's not, you need to (shim it)[https://github.com/thlorenz/browserify-shim#a-expose-global-variables-via-global]: +```javascript +{ + "browserify-shim": { + "jquery": "global:jQuery" + } +} +``` + ## Notes Modifiers are not case sensitive (`Ctrl` == `ctrl` == `cTRL`) From ba8ee3083668b5b65d9f455767c0d70ff7b1728a Mon Sep 17 00:00:00 2001 From: Lipis Date: Thu, 18 Sep 2014 18:01:51 +0200 Subject: [PATCH 44/59] Added bower.json file I'm not sure about the Licence or different things, but since the issues are not enabled for this project I just created a PR with the simplest one.. it is super useful and it looks like the original project is a bit dead.. --- bower.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 bower.json diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..c807c61 --- /dev/null +++ b/bower.json @@ -0,0 +1,10 @@ +{ + "name": "jQuery.Hotkeys", + "main": "jquery.hotkeys.js", + "ignore": [ + "test*" + ], + "dependencies": { + "jquery": "~2" + } +} From 06b796c1665db43c350a6554b3cd60f9b4efdeae Mon Sep 17 00:00:00 2001 From: Lipis Date: Tue, 30 Sep 2014 10:30:06 +0200 Subject: [PATCH 45/59] Added more into ignore list --- bower.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bower.json b/bower.json index c807c61..7452b39 100644 --- a/bower.json +++ b/bower.json @@ -2,6 +2,8 @@ "name": "jQuery.Hotkeys", "main": "jquery.hotkeys.js", "ignore": [ + "hotkeys.jquery.json", + "jquery-*", "test*" ], "dependencies": { From 1b360b4f85f5a4270dbcc3eb2b419a89cb45edfc Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 30 Sep 2014 18:55:36 +1000 Subject: [PATCH 46/59] Updated bower.json (mainly from hotkeys.jquery.json) --- bower.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bower.json b/bower.json index 7452b39..60a737c 100644 --- a/bower.json +++ b/bower.json @@ -1,12 +1,19 @@ { - "name": "jQuery.Hotkeys", + "name": "jquery.hotkeys", + "description": "jQuery Hotkeys lets you watch for keyboard events anywhere in your code supporting almost any key combination.", + "homepage": "http://github.com/jeresig/jquery.hotkeys", + "repository": "git://github.com/jeresig/jquery.hotkeys.git", + "keywords": ["jquery", "hotkeys", "keyboard", "events", "key", "bindings"], + "authors": ["John Resig (http://ejohn.org)"], "main": "jquery.hotkeys.js", "ignore": [ "hotkeys.jquery.json", "jquery-*", - "test*" + "test*", + "*.yml" ], "dependencies": { - "jquery": "~2" - } -} + "jquery": ">= 1.4.2" + }, + "license": ["MIT", "GPL-2.0"] +} \ No newline at end of file From a63c980f384e2d279d6a53a9d980ab02d513bd01 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 30 Sep 2014 22:19:53 +1000 Subject: [PATCH 47/59] Tweaking ignore on bower.json --- bower.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 60a737c..405ef30 100644 --- a/bower.json +++ b/bower.json @@ -7,10 +7,12 @@ "authors": ["John Resig (http://ejohn.org)"], "main": "jquery.hotkeys.js", "ignore": [ - "hotkeys.jquery.json", "jquery-*", "test*", - "*.yml" + "*.yml", + ".gitignore", + "Gruntfile.js", + "*.json" ], "dependencies": { "jquery": ">= 1.4.2" From f2d7bfad1ed832ce1cc52a4f3d92f28ff35bbbb1 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Fri, 17 Oct 2014 23:40:41 +1100 Subject: [PATCH 48/59] Beautifying jquery.hotkeys.js --- jquery.hotkeys.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 1822ecd..6ff4013 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -140,8 +140,8 @@ handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || - (jQuery.hotkeys.options.filterTextInputs && - jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { + (jQuery.hotkeys.options.filterTextInputs && + jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { return; } From 05a1370a55fac597ffe538c38e5f67bcb010450d Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Fri, 17 Oct 2014 23:41:06 +1100 Subject: [PATCH 49/59] Added bower_components to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b7c33b6..53d3df6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .idea -.grunt \ No newline at end of file +.grunt +bower_components From 9164e50ba4178d6636a33c155bb1bf1ada7f8962 Mon Sep 17 00:00:00 2001 From: Tyler Reinhard Date: Wed, 7 Jan 2015 01:21:09 -0500 Subject: [PATCH 50/59] Update jquery.hotkeys.js should also escape text-accepting inputs like search fields unless explicitly bound --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 6ff4013..dd31c50 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -139,7 +139,7 @@ handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to - if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || + if (this !== event.target && (/textarea|input|select/i.test(event.target.nodeName) || (jQuery.hotkeys.options.filterTextInputs && jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { return; From efee6d4d87f27e4aa593117211e73ecde4035e61 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 13 Jan 2015 21:20:20 +1100 Subject: [PATCH 51/59] Added test for not binding to input[search] unless done explicitly. --- test/spec/keyBindingSpec.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/test/spec/keyBindingSpec.js b/test/spec/keyBindingSpec.js index a2d6309..5d7ec3b 100644 --- a/test/spec/keyBindingSpec.js +++ b/test/spec/keyBindingSpec.js @@ -11,14 +11,22 @@ describe("binding functions to key combinations", function() { this.fixture = $('
'); $('body').append(this.fixture); + this.createEl = function(type, id, extra) { + var $el = $('<' + type + '" id="' + id + ' ' + extra + '" />'); + this.fixture.append($el); + return $el; + } + this.createInputEl = function(type, id) { - var $el = $(''); + var $el = this.createEl('input', id, 'type="' + type); this.fixture.append($el); return $el; }; + this.all_input_types = ["input", "select", "textarea"]; + this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", - "time", "datetime", "datetime-local", "search", "color", "tel"]; + "time", "datetime", "datetime-local", "search", "color", "tel", "search"]; // creates new key event this.createKeyEvent = function(keyCode, keyEventType) { @@ -105,6 +113,27 @@ describe("binding functions to key combinations", function() { this.assertHotKeyBinding('keyup', 'hyper+a', 65, ['alt', 'ctrl', 'meta', 'shift']); }); + it("should not trigger event handler callbacks bound to any input types if not bound directly", function() { + + var i = 0; + + _.each(this.all_input_types, function(input_type) { + + var spy = sinon.spy(); + + var $el = this.createEl(input_type, ++i); + $(document).bind('keyup', 'a', spy); + + var event = this.createKeyEvent('65', 'keyup'); + $el.trigger(event); + + sinon.assert.notCalled(spy); + $(document).unbind(); + $el.remove(); + + }, this); + }); + it("should not trigger event handler callbacks bound to any standard input types if not bound directly", function() { var i = 0; From 0e700f8a4031a469462badf84ae20cee3294b005 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 13 Jan 2015 21:27:47 +1100 Subject: [PATCH 52/59] Enable default input types only to be bound directly to be configurable. --- jquery.hotkeys.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index dd31c50..82578df 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -117,6 +117,9 @@ "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel"], + // default input types not to bind to unless bound directly + textInputTypes: /textarea|input|select/i, + options: { filterTextInputs: true } @@ -139,7 +142,7 @@ handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to - if (this !== event.target && (/textarea|input|select/i.test(event.target.nodeName) || + if (this !== event.target && (jQuery.hotkeys.textInputTypes.test(event.target.nodeName) || (jQuery.hotkeys.options.filterTextInputs && jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { return; From a4cdf9fe939ba6e8821ab5f1286b38000ff9f834 Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 13 Jan 2015 21:42:22 +1100 Subject: [PATCH 53/59] Excluded `contenteditable` fields by default and added to specs. Thanks to @cagataygurturk for pointing this out. --- jquery.hotkeys.js | 4 +++- test/spec/keyBindingSpec.js | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 82578df..51f1a39 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -121,7 +121,8 @@ textInputTypes: /textarea|input|select/i, options: { - filterTextInputs: true + filterTextInputs: true, + filterContentEditable: true } }; @@ -143,6 +144,7 @@ handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to if (this !== event.target && (jQuery.hotkeys.textInputTypes.test(event.target.nodeName) || + (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) || (jQuery.hotkeys.options.filterTextInputs && jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { return; diff --git a/test/spec/keyBindingSpec.js b/test/spec/keyBindingSpec.js index 5d7ec3b..8c59963 100644 --- a/test/spec/keyBindingSpec.js +++ b/test/spec/keyBindingSpec.js @@ -12,7 +12,8 @@ describe("binding functions to key combinations", function() { $('body').append(this.fixture); this.createEl = function(type, id, extra) { - var $el = $('<' + type + '" id="' + id + ' ' + extra + '" />'); + extra = extra || ''; + var $el = $('<' + type + ' id="' + id + ' ' + extra + '" />'); this.fixture.append($el); return $el; } @@ -23,7 +24,7 @@ describe("binding functions to key combinations", function() { return $el; }; - this.all_input_types = ["input", "select", "textarea"]; + this.all_input_types = ["input", "select", "textarea", "div contenteditable=\"true\""]; this.text_input_types = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color", "tel", "search"]; From 8be446274e9321e65361058669a4a302ea254c3d Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 13 Jan 2015 23:08:27 +1100 Subject: [PATCH 54/59] Added flag to control enable / disable of direct element binding #51. --- jquery.hotkeys.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 51f1a39..936fde3 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -121,6 +121,7 @@ textInputTypes: /textarea|input|select/i, options: { + filterInputAcceptingElements: true, filterTextInputs: true, filterContentEditable: true } @@ -143,7 +144,9 @@ handleObj.handler = function(event) { // Don't fire in text-accepting inputs that we didn't directly bind to - if (this !== event.target && (jQuery.hotkeys.textInputTypes.test(event.target.nodeName) || + if (this !== event.target && + (jQuery.hotkeys.options.filterInputAcceptingElements && + jQuery.hotkeys.textInputTypes.test(event.target.nodeName) || (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) || (jQuery.hotkeys.options.filterTextInputs && jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { From 818081bbcb04b1916f11472c51564e39561009ee Mon Sep 17 00:00:00 2001 From: Robert Pocklington Date: Tue, 13 Jan 2015 23:15:46 +1100 Subject: [PATCH 55/59] Updated README.md in line with new input configuration options. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 6511f24..9e52774 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,15 @@ Hotkeys aren't tracked if you're inside of an input element (unless you explicit You can use namespacing by adding a suffix to the event type (e.g. `keyup.toggle`) +## Hotkeys within inputs + +Hotkeys aren't tracked if the user is focused within an input element or any element that has `contenteditable="true"` unless you bind the hotkey directly to the element. This helps to avoid conflict with normal user typing. +If you don't want this, you can change the booleans of the following to suit: + + * `jQuery.hotkeys.options.filterInputAcceptingElements` + * `jQuery.hotkeys.options.filterContentEditable` + * `jQuery.hotkeys.options.filterTextInputs` (deprecated, will be removed in a later version) + ### Meta and Hyper Keys Meta and hyper keys don't register on `keyup` in any browser tested. From 0d75b7907961d9f60ad1e3bb82f146cc7a3a8996 Mon Sep 17 00:00:00 2001 From: Iulian Onofrei Date: Thu, 21 May 2015 16:28:32 +0300 Subject: [PATCH 56/59] Update README.md Fixed url markdown formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e52774..c8307f6 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ If you want to include this module in a Browserified project, just add it to nod require('jquery.javascript'); ``` -This will work if jQuery is global (ex. served from a CDN). If it's not, you need to (shim it)[https://github.com/thlorenz/browserify-shim#a-expose-global-variables-via-global]: +This will work if jQuery is global (ex. served from a CDN). If it's not, you need to [shim it](https://github.com/thlorenz/browserify-shim#a-expose-global-variables-via-global): ```javascript { "browserify-shim": { From 58038e6b47bb067619ac6448ce697990e4d9f52c Mon Sep 17 00:00:00 2001 From: Tyler Hughes Date: Sun, 8 Nov 2015 09:53:14 -0600 Subject: [PATCH 57/59] Fixed version number --- jquery.hotkeys.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.hotkeys.js b/jquery.hotkeys.js index 3e5095e..e7701f3 100644 --- a/jquery.hotkeys.js +++ b/jquery.hotkeys.js @@ -21,7 +21,7 @@ (function(jQuery) { jQuery.hotkeys = { - version: "0.8", + version: "0.2.0", specialKeys: { 8: "backspace", From c1907c0f5002810a27fc6f1cecf43bd735595898 Mon Sep 17 00:00:00 2001 From: Cheon Sangwon Date: Sun, 6 Dec 2015 05:32:35 +0900 Subject: [PATCH 58/59] Fixed version number from 0.1.0 to 0.2.0 --- hotkeys.jquery.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hotkeys.jquery.json b/hotkeys.jquery.json index d022a7e..6d60dfa 100644 --- a/hotkeys.jquery.json +++ b/hotkeys.jquery.json @@ -6,7 +6,7 @@ "keyboard", "events" ], - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "John Resig", "url": "https://ejohn.org/" diff --git a/package.json b/package.json index eefbba8..1f74c7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.hotkeys", - "version": "0.1.0", + "version": "0.2.0", "description": "**jQuery Hotkeys** is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.", "main": "jquery.hotkeys.js", "devDependencies": { From b8aa61025d4a2df6c3f7f33a4f3d8616e76ce914 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Mon, 7 Sep 2020 20:33:31 +1000 Subject: [PATCH 59/59] docs: Fix simple typo, omited -> omitted There is a small typo in jquery-1.4.2.js. Should read `omitted` rather than `omited`. --- jquery-1.4.2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery-1.4.2.js b/jquery-1.4.2.js index fff6776..63a1c43 100644 --- a/jquery-1.4.2.js +++ b/jquery-1.4.2.js @@ -4886,7 +4886,7 @@ jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".sp jQuery.extend({ get: function( url, data, callback, type ) { - // shift arguments if data argument was omited + // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; @@ -4911,7 +4911,7 @@ jQuery.extend({ }, post: function( url, data, callback, type ) { - // shift arguments if data argument was omited + // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data;