From 0b6afcedd22aaffb96d3d45b9b220a16229e2f7c Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Thu, 23 Dec 2010 16:21:14 -0500 Subject: When a native browser event is bubbling up the DOM, make sure that the correct isDefaultPrevented value is reflected by jQuery's Event object. Fixes #7793. --- test/unit/event.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'test') diff --git a/test/unit/event.js b/test/unit/event.js index a647e5f3b..d0183f89d 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -295,6 +295,44 @@ test("live/delegate immediate propagation", function() { $p.undelegate( "click" ); }); +test("bind/delegate bubbling, isDefaultPrevented", function() { + expect(2); + var $anchor2 = jQuery( "#anchor2" ), + $main = jQuery( "#main" ), + fakeClick = function($jq) { + // Prefer a native click so we don't get jQuery simulated bubbling + if ( $jq[0].click ) { + $jq[0].click(); // IE + } + else if ( document.createEvent ) { + var e = document.createEvent( 'MouseEvents' ); + e.initEvent( "click", true, true ); + $jq[0].dispatchEvent(e); + } + else { + $jq.click(); + } + }; + $anchor2.click(function(e) { + e.preventDefault(); + }); + $main.delegate("#foo", "click", function(e) { + equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); + $anchor2.click(function(e) { + // Let the default action occur + }); + $main.delegate("#foo", "click", function(e) { + equals( e.isDefaultPrevented(), false, "isDefaultPrevented false passed to bubbled event" ); + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); +}); + test("bind(), iframes", function() { // events don't work with iframes, see #939 - this test fails in IE because of contentDocument var doc = jQuery("#loadediframe").contents(); -- cgit v1.2.3 From b8931f744859e598aad52140d8ca085e013ac798 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Mon, 27 Dec 2010 12:43:48 -0500 Subject: Test for standard createEvent before IE-specific click method. Don't fallback to fake click; let's see which browsers fail, if any. --- test/unit/event.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/unit/event.js b/test/unit/event.js index d0183f89d..56bc64ff6 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -300,17 +300,14 @@ test("bind/delegate bubbling, isDefaultPrevented", function() { var $anchor2 = jQuery( "#anchor2" ), $main = jQuery( "#main" ), fakeClick = function($jq) { - // Prefer a native click so we don't get jQuery simulated bubbling - if ( $jq[0].click ) { - $jq[0].click(); // IE - } - else if ( document.createEvent ) { + // Use a native click so we don't get jQuery simulated bubbling + if ( document.createEvent ) { var e = document.createEvent( 'MouseEvents' ); e.initEvent( "click", true, true ); $jq[0].dispatchEvent(e); } - else { - $jq.click(); + else if ( $jq[0].click ) { + $jq[0].click(); // IE } }; $anchor2.click(function(e) { -- cgit v1.2.3 From 8099cdce800d45109b961e3521fc0080e9a876c6 Mon Sep 17 00:00:00 2001 From: rwldrn Date: Wed, 5 Jan 2011 13:32:59 -0500 Subject: Bug #7608 elem.runtimeStyle throws exception in Opera --- src/css.js | 12 ++++++++---- test/unit/css.js | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/src/css.js b/src/css.js index 8a83c6072..19c6342d2 100644 --- a/src/css.js +++ b/src/css.js @@ -263,8 +263,9 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards @@ -275,16 +276,19 @@ if ( document.documentElement.currentStyle ) { if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; - elem.runtimeStyle.left = rsLeft; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } } return ret === "" ? "auto" : ret; diff --git a/test/unit/css.js b/test/unit/css.js index fbbf937ca..edc340ceb 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -320,3 +320,25 @@ test(":visible selector works properly on children with a hidden parent (bug #45 jQuery('#table').css('display', 'none').html('cellcell'); equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible"); }); + +test("internal ref to elem.runtimeStyle (bug #7608)", function () { + expect(1); + + var result = true, + val = 10; + + jQuery('
' + + '
 
').appendTo("body"); + + try { + // the bug is located within src/css.js + jQuery("#bug7608 #test").animate( { width: val }, 1000); + + } catch (e) { + result = false; + } + + ok( result, "elem.runtimeStyle does not throw exception" ); + + jQuery("#bug7608").remove(); +}); -- cgit v1.2.3 From 8e59a99e0ade75dec434f246f52e8b3f7393f359 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 15:52:33 -0600 Subject: Change the way jQuery.data works so that there is no longer a chance of collision between user data and internal data. Fixes #6968. --- src/attributes.js | 4 +- src/data.js | 162 +++++++++++++++++++++----------- src/effects.js | 10 +- src/event.js | 46 +++++----- src/manipulation.js | 35 ++++--- src/queue.js | 4 +- test/unit/attributes.js | 10 +- test/unit/data.js | 238 ++++++++++++++++++++++++++++++++---------------- test/unit/effects.js | 4 +- test/unit/event.js | 20 ++-- 10 files changed, 341 insertions(+), 192 deletions(-) (limited to 'test') diff --git a/src/attributes.js b/src/attributes.js index fec132340..d37400a68 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -133,11 +133,11 @@ jQuery.fn.extend({ } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set - jQuery.data( this, "__className__", this.className ); + jQuery._data( this, "__className__", this.className ); } // toggle whole className - this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, diff --git a/src/data.js b/src/data.js index 4d1d1bd55..a1abc9ed6 100644 --- a/src/data.js +++ b/src/data.js @@ -1,7 +1,6 @@ (function( jQuery ) { -var windowData = {}, - rbrace = /^(?:\{.*\}|\[.*\])$/; +var rbrace = /^(?:\{.*\}|\[.*\])$/; jQuery.extend({ cache: {}, @@ -23,110 +22,163 @@ jQuery.extend({ }, hasData: function( elem ) { - if ( elem.nodeType ) { - elem = jQuery.cache[ elem[jQuery.expando] ]; - } + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !jQuery.isEmptyObject(elem); }, - data: function( elem, name, data ) { + data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : null, - cache = jQuery.cache, thisCache; + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - if ( isNode && !id && typeof name === "string" && data === undefined ) { + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { return; } - // Get the data from the object directly - if ( !isNode ) { - cache = elem; + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } - // Compute a unique ID for the element - } else if ( !id ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; + if ( !cache[ id ] ) { + cache[ id ] = {}; } - // Avoid generating a new cache unless none exists and we - // want to manipulate it. + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache if ( typeof name === "object" ) { - if ( isNode ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { cache[ id ] = jQuery.extend(cache[ id ], name); + } + } - } else { - jQuery.extend( cache, name ); + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; } - } else if ( isNode && !cache[ id ] ) { - cache[ id ] = {}; + thisCache = thisCache[ internalKey ]; } - thisCache = isNode ? cache[ id ] : cache; - - // Prevent overriding the named cache with undefined values if ( data !== undefined ) { thisCache[ name ] = data; } - return typeof name === "string" ? thisCache[ name ] : thisCache; + return getByName ? thisCache[ name ] : thisCache; }, - removeData: function( elem, name ) { + removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : elem, - cache = jQuery.cache, - thisCache = isNode ? cache[ id ] : id; + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } - // If we want to remove a specific section of the element's data if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + if ( thisCache ) { - // Remove the section of cache data delete thisCache[ name ]; - // If we've removed all the data, remove the element's cache - if ( isNode && jQuery.isEmptyObject(thisCache) ) { - jQuery.removeData( elem ); + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !jQuery.isEmptyObject(thisCache) ) { + return; } } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !jQuery.isEmptyObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; - // Otherwise, we want to remove all of the element's data + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; } else { - if ( isNode && jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; + cache[ id ] = null; + } + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); - - // Completely remove the data cache - } else if ( isNode ) { - delete cache[ id ]; - - // Remove all fields from the object } else { - for ( var n in elem ) { - delete elem[ n ]; - } + elem[ jQuery.expando ] = null; } } }, + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { diff --git a/src/effects.js b/src/effects.js index bd57ffc3d..b0675395f 100644 --- a/src/effects.js +++ b/src/effects.js @@ -27,7 +27,7 @@ jQuery.fn.extend({ // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; } @@ -35,7 +35,7 @@ jQuery.fn.extend({ // in a stylesheet to whatever the default browser style is // for such an element if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); } } @@ -46,7 +46,7 @@ jQuery.fn.extend({ display = elem.style.display; if ( display === "" || display === "none" ) { - elem.style.display = jQuery.data(elem, "olddisplay") || ""; + elem.style.display = jQuery._data(elem, "olddisplay") || ""; } } @@ -62,8 +62,8 @@ jQuery.fn.extend({ for ( var i = 0, j = this.length; i < j; i++ ) { var display = jQuery.css( this[i], "display" ); - if ( display !== "none" && !jQuery.data( this[i], "olddisplay" ) ) { - jQuery.data( this[i], "olddisplay", display ); + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); } } diff --git a/src/event.js b/src/event.js index 675e5fff3..7fa5488ee 100644 --- a/src/event.js +++ b/src/event.js @@ -8,7 +8,8 @@ var rnamespaces = /\.(.*)$/, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); }, - focusCounts = { focusin: 0, focusout: 0 }; + focusCounts = { focusin: 0, focusout: 0 }, + eventKey = "events"; /* * A number of helper functions used for managing events. @@ -50,7 +51,7 @@ jQuery.event = { } // Init the element's event structure - var elemData = jQuery.data( elem ); + var elemData = jQuery._data( elem ); // If no elemData is found then we must be trying to bind to one of the // banned noData elements @@ -58,10 +59,7 @@ jQuery.event = { return; } - // Use a key less likely to result in collisions for plain JS objects. - // Fixes bug #7150. - var eventKey = elem.nodeType ? "events" : "__events__", - events = elemData[ eventKey ], + var events = elemData[ eventKey ], eventHandle = elemData.handle; if ( typeof events === "function" ) { @@ -177,8 +175,7 @@ jQuery.event = { } var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - eventKey = elem.nodeType ? "events" : "__events__", - elemData = jQuery.data( elem ), + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), events = elemData && elemData[ eventKey ]; if ( !elemData || !events ) { @@ -290,10 +287,10 @@ jQuery.event = { delete elemData.handle; if ( typeof elemData === "function" ) { - jQuery.removeData( elem, eventKey ); + jQuery.removeData( elem, eventKey, true ); } else if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem ); + jQuery.removeData( elem, undefined, true ); } } }, @@ -325,9 +322,16 @@ jQuery.event = { // Only trigger if we've ever bound an event for it if ( jQuery.event.global[ type ] ) { + // XXX This code smells terrible. event.js should not be directly + // inspecting the data cache jQuery.each( jQuery.cache, function() { - if ( this.events && this.events[type] ) { - jQuery.event.trigger( event, data, this.handle.elem ); + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[type] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); } }); } @@ -353,8 +357,8 @@ jQuery.event = { // Trigger the event, it is assumed that "handle" is a function var handle = elem.nodeType ? - jQuery.data( elem, "handle" ) : - (jQuery.data( elem, "__events__" ) || {}).handle; + jQuery._data( elem, "handle" ) : + (jQuery._data( elem, eventKey ) || {}).handle; if ( handle ) { handle.apply( elem, data ); @@ -432,7 +436,7 @@ jQuery.event = { event.namespace = event.namespace || namespace_sort.join("."); - events = jQuery.data(this, this.nodeType ? "events" : "__events__"); + events = jQuery._data(this, eventKey); if ( typeof events === "function" ) { events = events.events; @@ -787,12 +791,12 @@ if ( !jQuery.support.changeBubbles ) { return; } - data = jQuery.data( elem, "_change_data" ); + data = jQuery._data( elem, "_change_data" ); val = getVal(elem); // the current data will be also retrieved by beforeactivate if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery.data( elem, "_change_data", val ); + jQuery._data( elem, "_change_data", val ); } if ( data === undefined || val === data ) { @@ -837,7 +841,7 @@ if ( !jQuery.support.changeBubbles ) { // information beforeactivate: function( e ) { var elem = e.target; - jQuery.data( elem, "_change_data", getVal(elem) ); + jQuery._data( elem, "_change_data", getVal(elem) ); } }, @@ -986,8 +990,8 @@ jQuery.fn.extend({ return this.click( jQuery.proxy( fn, function( event ) { // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); @@ -1075,7 +1079,7 @@ function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], - events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + events = jQuery._data( this, eventKey ); if ( typeof events === "function" ) { events = events.events; diff --git a/src/manipulation.js b/src/manipulation.js index 96caa02d0..657aef7d1 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -381,17 +381,24 @@ function cloneCopyEvent(orig, ret) { throw "Cloned data mismatch"; } - var oldData = jQuery.data( orig[nodeIndex] ), - curData = jQuery.data( this, oldData ), - events = oldData && oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( var type in events ) { - for ( var i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( this, type, events[ type ][ i ], events[ type ][ i ].data ); + var internalKey = jQuery.expando, + oldData = jQuery.data( orig[nodeIndex] ), + curData = jQuery.data( this, oldData ); + + // Switch to use the internal data object, if it exists, for the next + // stage of data copying + if ( (oldData = oldData[ internalKey ]) ) { + var events = oldData.events; + curData = curData[ internalKey ] = jQuery.extend({}, oldData); + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( this, type, events[ type ][ i ], events[ type ][ i ].data ); + } } } } @@ -594,8 +601,7 @@ jQuery.extend({ }, cleanData: function( elems ) { - var data, id, cache = jQuery.cache, - special = jQuery.event.special, + var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { @@ -606,13 +612,14 @@ jQuery.extend({ id = elem[ jQuery.expando ]; if ( id ) { - data = cache[ id ]; + data = cache[ id ] && cache[ id ][ internalKey ]; if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); + // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } diff --git a/src/queue.js b/src/queue.js index 735b0e189..5fb04df80 100644 --- a/src/queue.js +++ b/src/queue.js @@ -7,7 +7,7 @@ jQuery.extend({ } type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type ); + var q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( !data ) { @@ -15,7 +15,7 @@ jQuery.extend({ } if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data) ); + q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); diff --git a/test/unit/attributes.js b/test/unit/attributes.js index a1ab58179..04f168466 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -703,12 +703,12 @@ var testToggleClass = function(valueObj) { // toggleClass storage e.toggleClass(true); - ok( e.get(0).className === "", "Assert class is empty (data was empty)" ); + ok( e[0].className === "", "Assert class is empty (data was empty)" ); e.addClass("testD testE"); ok( e.is(".testD.testE"), "Assert class present" ); e.toggleClass(); ok( !e.is(".testD.testE"), "Assert class not present" ); - ok( e.data('__className__') === 'testD testE', "Assert data was stored" ); + ok( jQuery._data(e[0], '__className__') === 'testD testE', "Assert data was stored" ); e.toggleClass(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); e.toggleClass(false); @@ -720,11 +720,9 @@ var testToggleClass = function(valueObj) { e.toggleClass(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); - - // Cleanup e.removeClass("testD"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }; test("toggleClass(String|boolean|undefined[, boolean])", function() { @@ -785,7 +783,7 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() { // Cleanup e.removeClass("test"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }); test("addClass, removeClass, hasClass", function() { diff --git a/test/unit/data.js b/test/unit/data.js index 310cd6bc4..28e19a3e2 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -1,96 +1,177 @@ -module("data"); +module("data", { teardown: moduleTeardown }); test("expando", function(){ - expect(6); + expect(1); equals("expando" in jQuery, true, "jQuery is exposing the expando"); +}); - var obj = {}; - equals( jQuery.data(obj), obj, "jQuery.data(obj) returns the object"); - equals( jQuery.expando in obj, false, "jQuery.data(obj) did not add an expando to the object" ); +function dataTests (elem) { + // expect(32) - obj = {}; - jQuery.data(obj, 'test'); - equals( jQuery.expando in obj, false, "jQuery.data(obj,key) did not add an expando to the object" ); + function getCacheLength() { + var cacheLength = 0; + for (var i in jQuery.cache) { + ++cacheLength; + } - obj = {}; - jQuery.data(obj, "foo", "bar"); - equals( jQuery.expando in obj, false, "jQuery.data(obj,key,value) did not add an expando to the object" ); - equals( obj.foo, "bar", "jQuery.data(obj,key,value) sets fields directly on the object." ); -}); + return cacheLength; + } -test("jQuery.acceptData", function() { - expect(7); + equals( jQuery.data(elem, "foo"), undefined, "No data exists initially" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" ); - ok( jQuery.acceptData( document ), "document" ); - ok( jQuery.acceptData( document.documentElement ), "documentElement" ); - ok( jQuery.acceptData( {} ), "object" ); - ok( !jQuery.acceptData( document.createElement("embed") ), "embed" ); - ok( !jQuery.acceptData( document.createElement("applet") ), "applet" ); + var dataObj = jQuery.data(elem); + equals( typeof dataObj, "object", "Calling data with no args gives us a data object reference" ); + strictEqual( jQuery.data(elem), dataObj, "Calling jQuery.data returns the same data object when called multiple times" ); - var flash = document.createElement("object"); - flash.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"); - ok( jQuery.acceptData( flash ), "flash" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists even when an empty data obj exists" ); - var applet = document.createElement("object"); - applet.setAttribute("classid", "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"); - ok( !jQuery.acceptData( applet ), "applet" ); -}); + dataObj.foo = "bar"; + equals( jQuery.data(elem, "foo"), "bar", "Data is readable by jQuery.data when set directly on a returned data object" ); -test("jQuery.data", function() { - expect(15); - var div = document.createElement("div"); + strictEqual( jQuery.hasData(elem), true, "jQuery.hasData agrees data exists when data exists" ); - ok( jQuery.data(div, "test") === undefined, "Check for no data exists" ); + jQuery.data(elem, "foo", "baz"); + equals( jQuery.data(elem, "foo"), "baz", "Data can be changed by jQuery.data" ); + equals( dataObj.foo, "baz", "Changes made through jQuery.data propagate to referenced data object" ); - jQuery.data(div, "test", "success"); - equals( jQuery.data(div, "test"), "success", "Check for added data" ); + jQuery.data(elem, "foo", undefined); + equals( jQuery.data(elem, "foo"), "baz", "Data is not unset by passing undefined to jQuery.data" ); - ok( jQuery.data(div, "notexist") === undefined, "Check for no data exists" ); + jQuery.data(elem, "foo", null); + strictEqual( jQuery.data(elem, "foo"), null, "Setting null using jQuery.data works OK" ); - var data = jQuery.data(div); - same( data, { "test": "success" }, "Return complete data set" ); + jQuery.data(elem, "foo", "foo1"); - jQuery.data(div, "test", "overwritten"); - equals( jQuery.data(div, "test"), "overwritten", "Check for overwritten data" ); + jQuery.data(elem, { "bar" : "baz", "boom" : "bloz" }); + strictEqual( jQuery.data(elem, "foo"), "foo1", "Passing an object extends the data object instead of replacing it" ); + equals( jQuery.data(elem, "boom"), "bloz", "Extending the data object works" ); - jQuery.data(div, "test", undefined); - equals( jQuery.data(div, "test"), "overwritten", "Check that data wasn't removed"); + jQuery._data(elem, "foo", "foo2"); + equals( jQuery._data(elem, "foo"), "foo2", "Setting internal data works" ); + equals( jQuery.data(elem, "foo"), "foo1", "Setting internal data does not override user data" ); - jQuery.data(div, "test", null); - ok( jQuery.data(div, "test") === null, "Check for null data"); + var internalDataObj = jQuery.data(elem, jQuery.expando); + strictEqual( jQuery._data(elem), internalDataObj, "Internal data object is accessible via jQuery.expando property" ); + notStrictEqual( dataObj, internalDataObj, "Internal data object is not the same as user data object" ); - jQuery.data(div, "test3", "orig"); - jQuery.data(div, { "test": "in", "test2": "in2" }); - equals( jQuery.data(div, "test"), "in", "Verify setting an object in data" ); - equals( jQuery.data(div, "test2"), "in2", "Verify setting an object in data" ); - equals( jQuery.data(div, "test3"), "orig", "Verify original not overwritten" ); + strictEqual( elem.boom, undefined, "Data is never stored directly on the object" ); - var obj = {}; - jQuery.data( obj, "prop", true ); + jQuery.removeData(elem, "foo"); + strictEqual( jQuery.data(elem, "foo"), undefined, "jQuery.removeData removes single properties" ); - ok( obj.prop, "Data is being stored on the object" ); - equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved" ); + jQuery.removeData(elem); + strictEqual( jQuery.data(elem, jQuery.expando), internalDataObj, "jQuery.removeData does not remove internal data if it exists" ); - jQuery.data( window, "BAD", true ); - ok( !window[ jQuery.expando ], "Make sure there is no expando on the window object." ); - ok( !window.BAD, "And make sure that the property wasn't set directly on the window." ); - ok( jQuery.data( window, "BAD" ), "Make sure that the value was set." ); -}); + jQuery.removeData(elem, undefined, true); -test("jQuery.hasData", function() { - expect(6); + strictEqual( jQuery.data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees all data has been removed from object" ); + + jQuery._data(elem, "foo", "foo2"); + strictEqual( jQuery.hasData(elem), true, "jQuery.hasData shows data exists even if it is only internal data" ); + + jQuery.data(elem, "foo", "foo1"); + equals( jQuery._data(elem, "foo"), "foo2", "Setting user data does not override internal data" ); + + jQuery.removeData(elem, undefined, true); + equals( jQuery.data(elem, "foo"), "foo1", "jQuery.removeData for internal data does not remove user data" ); + + if (elem.nodeType) { + var oldCacheLength = getCacheLength(); + jQuery.removeData(elem, "foo"); - function testData(obj) { - equals( jQuery.hasData(obj), false, "No data exists" ); - jQuery.data( obj, "foo", "bar" ); - equals( jQuery.hasData(obj), true, "Data exists" ); - jQuery.removeData( obj, "foo" ); - equals( jQuery.hasData(obj), false, "Data was removed" ); + equals( getCacheLength(), oldCacheLength - 1, "Removing the last item in the data object destroys it" ); } + else { + jQuery.removeData(elem, "foo"); + var expected, actual; + + if (jQuery.support.deleteExpando) { + expected = false; + actual = jQuery.expando in elem; + } + else { + expected = null; + actual = elem[ jQuery.expando ]; + } + + equals( actual, expected, "Removing the last item in the data object destroys it" ); + } + + jQuery.data(elem, "foo", "foo1"); + jQuery._data(elem, "foo", "foo2"); + + equals( jQuery.data(elem, "foo"), "foo1", "(sanity check) Ensure data is set in user data object" ); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); + + jQuery.removeData(elem, "foo", true); + + strictEqual( jQuery.data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); + + jQuery._data(elem, "foo", "foo2"); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); - testData(document.createElement('div')); - testData({}); + jQuery.removeData(elem, "foo"); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) jQuery.removeData for user data does not remove internal data" ); + + if (elem.nodeType) { + oldCacheLength = getCacheLength(); + jQuery.removeData(elem, "foo", true); + equals( getCacheLength(), oldCacheLength - 1, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); + } + else { + jQuery.removeData(elem, "foo", true); + + if (jQuery.support.deleteExpando) { + expected = false; + actual = jQuery.expando in elem; + } + else { + expected = null; + actual = elem[ jQuery.expando ]; + } + + equals( actual, expected, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); + } +} + +test("jQuery.data", function() { + expect(128); + + var div = document.createElement("div"); + + dataTests(div); + dataTests({}); + + // remove bound handlers from window object to stop potential false positives caused by fix for #5280 in + // transports/xhr.js + jQuery(window).unbind("unload"); + + dataTests(window); + dataTests(document); + + // clean up unattached element + jQuery(div).remove(); +}); + +test("jQuery.acceptData", function() { + expect(7); + + ok( jQuery.acceptData( document ), "document" ); + ok( jQuery.acceptData( document.documentElement ), "documentElement" ); + ok( jQuery.acceptData( {} ), "object" ); + ok( !jQuery.acceptData( document.createElement("embed") ), "embed" ); + ok( !jQuery.acceptData( document.createElement("applet") ), "applet" ); + + var flash = document.createElement("object"); + flash.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"); + ok( jQuery.acceptData( flash ), "flash" ); + + var applet = document.createElement("object"); + applet.setAttribute("classid", "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"); + ok( !jQuery.acceptData( applet ), "applet" ); }); test(".data()", function() { @@ -98,7 +179,6 @@ test(".data()", function() { var div = jQuery("#foo"); strictEqual( div.data("foo"), undefined, "Make sure that missing result is undefined" ); - div.data("test", "success"); same( div.data(), {test: "success"}, "data() get the entire data object" ); strictEqual( div.data("foo"), undefined, "Make sure that missing result is still undefined" ); @@ -107,7 +187,7 @@ test(".data()", function() { equals( nodiv.data(), null, "data() on empty set returns null" ); var obj = { foo: "bar" }; - equals( jQuery(obj).data(), obj, "Retrieve data object from a wrapped JS object (#7524)" ); + deepEqual( jQuery(obj).data(), {}, "Retrieve data object from a wrapped JS object (#7524)" ); }) test(".data(String) and .data(String, Object)", function() { @@ -194,11 +274,14 @@ test(".data(String) and .data(String, Object)", function() { equals( $elem.data('null',null).data('null'), null, "null's are preserved"); equals( $elem.data('emptyString','').data('emptyString'), '', "Empty strings are preserved"); equals( $elem.data('false',false).data('false'), false, "false's are preserved"); - equals( $elem.data('exists'), true, "Existing data is returned" ); + equals( $elem.data('exists'), undefined, "Existing data is not returned" ); // Clean up $elem.removeData(); - ok( jQuery.isEmptyObject( $elem[0] ), "removeData clears the object" ); + deepEqual( $elem[0], {exists:true}, "removeData does not clear the object" ); + + // manually clean up detached elements + parent.remove(); }); test("data-* attributes", function() { @@ -323,13 +406,17 @@ test(".data(Object)", function() { var obj = {test:"unset"}, jqobj = jQuery(obj); + jqobj.data("test", "unset"); jqobj.data({ "test": "in", "test2": "in2" }); - equals( obj.test, "in", "Verify setting an object on an object extends the object" ); - equals( obj.test2, "in2", "Verify setting an object on an object extends the object" ); + equals( jQuery.data(obj).test, "in", "Verify setting an object on an object extends the data object" ); + equals( obj.test2, undefined, "Verify setting an object on an object does not extend the object" ); + + // manually clean up detached elements + div.remove(); }); test("jQuery.removeData", function() { - expect(7); + expect(6); var div = jQuery("#foo")[0]; jQuery.data(div, "test", "testing"); jQuery.removeData(div, "test"); @@ -342,10 +429,9 @@ test("jQuery.removeData", function() { var obj = {}; jQuery.data(obj, "test", "testing"); - equals( obj.test, "testing", "verify data on plain object"); + equals( jQuery(obj).data("test"), "testing", "verify data on plain object"); jQuery.removeData(obj, "test"); equals( jQuery.data(obj, "test"), undefined, "Check removal of data on plain object" ); - equals( obj.test, undefined, "Check removal of data directly from plain object" ); jQuery.data( window, "BAD", true ); jQuery.removeData( window, "BAD" ); @@ -371,4 +457,4 @@ test(".removeData()", function() { div.removeData("test.foo"); equals( div.data("test.foo"), undefined, "Make sure data is intact" ); -}); +}); \ No newline at end of file diff --git a/test/unit/effects.js b/test/unit/effects.js index b7b60abbe..cf9d13109 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -895,7 +895,7 @@ test("hide hidden elements (bug #7141)", function() { var div = jQuery("
").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(); - ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); + ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); div.show(); equals( div.css("display"), "block", "Show a double-hidden element" ); @@ -910,7 +910,7 @@ test("hide hidden elements, with animation (bug #7141)", function() { var div = jQuery("
").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(1, function () { - ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); + ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); div.show(1, function () { equals( div.css("display"), "block", "Show a double-hidden element" ); start(); diff --git a/test/unit/event.js b/test/unit/event.js index b4672a8b8..cae1a83fe 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -28,7 +28,7 @@ test("bind(), with data", function() { }; jQuery("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); }); test("click(), with data", function() { @@ -39,7 +39,7 @@ test("click(), with data", function() { }; jQuery("#firstp").click({foo: "bar"}, handler).click().unbind("click", handler); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); }); test("bind(), with data, trigger with data", function() { @@ -505,7 +505,7 @@ test("bind(), with different this object", function() { .bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1) .bind("click", data, jQuery.proxy(handler2, thisObject)).click().unbind("click", handler2); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); }); test("bind(name, false), unbind(name, false)", function() { @@ -547,7 +547,7 @@ test("bind()/trigger()/unbind() on plain object", function() { } }); - var events = jQuery(obj).data("__events__"); + var events = jQuery._data(obj, "events"); ok( events, "Object has events bound." ); equals( obj.events, undefined, "Events object on plain objects is not events" ); equals( typeof events, "function", "'events' expando is a function on plain objects." ); @@ -567,7 +567,9 @@ test("bind()/trigger()/unbind() on plain object", function() { // Make sure it doesn't complain when no events are found jQuery(obj).unbind("test"); - equals( obj.__events__, undefined, "Make sure events object is removed" ); + equals( obj && obj[ jQuery.expando ] && + obj[ jQuery.expando ][ jQuery.expando ] && + obj[ jQuery.expando ][ jQuery.expando ].events, undefined, "Make sure events object is removed" ); }); test("unbind(type)", function() { @@ -947,7 +949,7 @@ test("toggle(Function, Function, ...)", function() { equals( turn, 2, "Trying toggle with 3 functions, attempt 5 yields 2"); $div.unbind('click',fns[0]); - var data = jQuery.data( $div[0], 'events' ); + var data = jQuery._data( $div[0], 'events' ); ok( !data, "Unbinding one function from toggle unbinds them all"); // Test Multi-Toggles @@ -1065,7 +1067,7 @@ test(".live()/.die()", function() { equals( clicked, 2, "live with a context" ); // Make sure the event is actually stored on the context - ok( jQuery.data(container, "events").live, "live with a context" ); + ok( jQuery._data(container, "events").live, "live with a context" ); // Test unbinding with a different context jQuery("#foo", container).die("click"); @@ -1578,7 +1580,7 @@ test(".delegate()/.undelegate()", function() { equals( clicked, 2, "delegate with a context" ); // Make sure the event is actually stored on the context - ok( jQuery.data(container, "events").live, "delegate with a context" ); + ok( jQuery._data(container, "events").live, "delegate with a context" ); // Test unbinding with a different context jQuery("#main").undelegate("#foo", "click"); @@ -1907,7 +1909,7 @@ test("window resize", function() { ok( true, "Resize event fired." ); }).resize().unbind("resize"); - ok( !jQuery(window).data("__events__"), "Make sure all the events are gone." ); + ok( !jQuery._data(window, "__events__"), "Make sure all the events are gone." ); }); test("focusin bubbles", function() { -- cgit v1.2.3 From e2941d5a98e91c5f61b200b2763e5fa0eb339365 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 15:58:47 -0600 Subject: Update unit tests with a leak detection mechanism for the various jQuery globals and fix all leaks in the tests. --- test/data/testinit.js | 49 ++++++++++++++++++++++++++++++++++++ test/index.html | 2 +- test/unit/ajax.js | 2 +- test/unit/attributes.js | 2 +- test/unit/core.js | 5 +++- test/unit/css.js | 2 +- test/unit/data.js | 4 +++ test/unit/dimensions.js | 18 +++++++++++++- test/unit/effects.js | 31 ++++++++++++++--------- test/unit/event.js | 41 +++++++++++++++++++++++++++--- test/unit/manipulation.js | 63 +++++++++++++++++++++++++++++++++++++---------- test/unit/offset.js | 2 +- test/unit/queue.js | 2 +- test/unit/selector.js | 2 +- test/unit/traversing.js | 2 +- 15 files changed, 189 insertions(+), 38 deletions(-) (limited to 'test') diff --git a/test/data/testinit.js b/test/data/testinit.js index a66f71d25..c478390d5 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -45,3 +45,52 @@ function t(a,b,c) { function url(value) { return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); } + +(function () { + // Store the old counts so that we only assert on tests that have actually leaked, + // instead of asserting every time a test has leaked sometime in the past + var oldCacheLength = 0, + oldFragmentsLength = 0, + oldTimersLength = 0, + oldActive = 0; + + /** + * Ensures that tests have cleaned up properly after themselves. Should be passed as the + * teardown function on all modules' lifecycle object. + */ + this.moduleTeardown = function () { + var i, fragmentsLength = 0, cacheLength = 0; + + // Allow QUnit.reset to clean up any attached elements before checking for leaks + QUnit.reset(); + + for ( i in jQuery.cache ) { + ++cacheLength; + } + + jQuery.fragments = {}; + + for ( i in jQuery.fragments ) { + ++fragmentsLength; + } + + // Because QUnit doesn't have a mechanism for retrieving the number of expected assertions for a test, + // if we unconditionally assert any of these, the test will fail with too many assertions :| + if ( cacheLength !== oldCacheLength ) { + equals( cacheLength, oldCacheLength, "No unit tests leak memory in jQuery.cache" ); + oldCacheLength = cacheLength; + } + if ( fragmentsLength !== oldFragmentsLength ) { + equals( fragmentsLength, oldFragmentsLength, "No unit tests leak memory in jQuery.fragments" ); + oldFragmentsLength = fragmentsLength; + } + if ( jQuery.timers.length !== oldTimersLength ) { + equals( jQuery.timers.length, oldTimersLength, "No timers are still running" ); + oldTimersLength = jQuery.timers.length; + } + if ( jQuery.active !== oldActive ) { + equals( jQuery.active, 0, "No AJAX requests are still active" ); + oldActive = jQuery.active; + } + } +}()); \ No newline at end of file diff --git a/test/index.html b/test/index.html index bbeda63a6..fc5f667d2 100644 --- a/test/index.html +++ b/test/index.html @@ -264,7 +264,7 @@ Z
slideToggleOut
slideToggleOut
fadeToggleIn
fadeToggleIn
-
fadeToggleOut
fadeToggleOut
+
fadeToggleOut
fadeToggleOut
fadeTo
fadeTo
diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 773088fc9..d29eddbfa 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1,4 +1,4 @@ -module("ajax"); +module("ajax", { teardown: moduleTeardown }); // Safari 3 randomly crashes when running these tests, // but only in the full suite - you can run just the Ajax diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 04f168466..c58111de1 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -1,4 +1,4 @@ -module("attributes"); +module("attributes", { teardown: moduleTeardown }); var bareObj = function(value) { return value; }; var functionReturningObj = function(value) { return (function() { return value; }); }; diff --git a/test/unit/core.js b/test/unit/core.js index 8fd060578..6231b1d53 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1,4 +1,4 @@ -module("core"); +module("core", { teardown: moduleTeardown }); test("Basic requirements", function() { expect(7); @@ -84,6 +84,9 @@ test("jQuery()", function() { exec = true; elem.click(); + + // manually clean up detached elements + elem.remove(); }); test("selector state", function() { diff --git a/test/unit/css.js b/test/unit/css.js index fbbf937ca..063707196 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -1,4 +1,4 @@ -module("css"); +module("css", { teardown: moduleTeardown }); test("css(String|Hash)", function() { expect(41); diff --git a/test/unit/data.js b/test/unit/data.js index 28e19a3e2..889fc2da3 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -301,6 +301,8 @@ test("data-* attributes", function() { div.data("attr", "internal").attr("data-attr", "external"); equals( div.data("attr"), "internal", "Check for .data('attr') precedence (internal > external data-* attribute)" ); + div.remove(); + child.appendTo('#main'); equals( child.data("myobj"), "old data", "Value accessed from data-* attribute"); @@ -312,6 +314,8 @@ test("data-* attributes", function() { var obj = child.data(), obj2 = dummy.data(), check = [ "myobj", "ignored", "other" ], num = 0, num2 = 0; + dummy.remove(); + for ( var i = 0, l = check.length; i < l; i++ ) { ok( obj[ check[i] ], "Make sure data- property exists when calling data-." ); ok( obj2[ check[i] ], "Make sure data- property exists when calling data-." ); diff --git a/test/unit/dimensions.js b/test/unit/dimensions.js index b38e73bba..fa59a9f77 100644 --- a/test/unit/dimensions.js +++ b/test/unit/dimensions.js @@ -1,4 +1,4 @@ -module("dimensions"); +module("dimensions", { teardown: moduleTeardown }); function pass( val ) { return val; @@ -33,6 +33,8 @@ function testWidth( val ) { var blah = jQuery("blah"); equals( blah.width( val(10) ), blah, "Make sure that setting a width on an empty set returns the set." ); equals( blah.width(), null, "Make sure 'null' is returned on an empty set"); + + jQuery.removeData($div[0], 'olddisplay', true); } test("width()", function() { @@ -80,6 +82,8 @@ function testHeight( val ) { var blah = jQuery("blah"); equals( blah.height( val(10) ), blah, "Make sure that setting a height on an empty set returns the set." ); equals( blah.height(), null, "Make sure 'null' is returned on an empty set"); + + jQuery.removeData($div[0], 'olddisplay', true); } test("height()", function() { @@ -126,6 +130,9 @@ test("innerWidth()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.innerWidth(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("innerHeight()", function() { @@ -152,6 +159,9 @@ test("innerHeight()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.innerHeight(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("outerWidth()", function() { @@ -179,6 +189,9 @@ test("outerWidth()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.outerWidth(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("outerHeight()", function() { @@ -205,4 +218,7 @@ test("outerHeight()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.outerHeight(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); diff --git a/test/unit/effects.js b/test/unit/effects.js index cf9d13109..b1dd28840 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -1,4 +1,4 @@ -module("effects"); +module("effects", { teardown: moduleTeardown }); test("sanity check", function() { expect(1); @@ -14,7 +14,7 @@ test("show()", function() { equals( hiddendiv.css("display"), "block", "Make sure a pre-hidden div is visible." ); - var div = jQuery("
").hide().appendTo("body").show(); + var div = jQuery("
").hide().appendTo("#main").show(); equal( div.css("display"), "block", "Make sure pre-hidden divs show" ); @@ -403,13 +403,16 @@ test("animate duration 0", function() { $elem.hide(0, function(){ ok(true, "Hide callback with no duration"); }); + + // manually clean up detached elements + $elem.remove(); }); test("animate hyphenated properties", function(){ expect(1); stop(); - jQuery("#nothiddendiv") + jQuery("#foo") .css("font-size", 10) .animate({"font-size": 20}, 200, function(){ equals( this.style.fontSize, "20px", "The font-size property was animated." ); @@ -433,7 +436,7 @@ test("stop()", function() { expect(3); stop(); - var $foo = jQuery("#nothiddendiv"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -446,6 +449,8 @@ test("stop()", function() { nw = $foo.width(); notEqual( nw, w, "Stop didn't reset the animation " + nw + "px " + w + "px"); setTimeout(function(){ + $foo.removeData(); + $foo.removeData(undefined, true); equals( nw, $foo.width(), "The animation didn't continue" ); start(); }, 100); @@ -456,7 +461,7 @@ test("stop() - several in queue", function() { expect(3); stop(); - var $foo = jQuery("#nothiddendivchild"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -481,7 +486,7 @@ test("stop(clearQueue)", function() { expect(4); stop(); - var $foo = jQuery("#nothiddendiv"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -508,7 +513,7 @@ test("stop(clearQueue, gotoEnd)", function() { expect(1); stop(); - var $foo = jQuery("#nothiddendivchild"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -536,7 +541,7 @@ test("stop(clearQueue, gotoEnd)", function() { test("toggle()", function() { expect(6); - var x = jQuery("#nothiddendiv"); + var x = jQuery("#foo"); ok( x.is(":visible"), "is visible" ); x.toggle(); ok( x.is(":hidden"), "is hidden" ); @@ -737,6 +742,9 @@ jQuery.each( { } } + // manually remove generated element + jQuery(this).remove(); + start(); }); }); @@ -763,6 +771,10 @@ jQuery.checkState = function(){ var cur = self.style[ c ] || jQuery.css(self, c); equals( cur, v, "Make sure that " + c + " is reset (Old: " + v + " Cur: " + cur + ")"); }); + + // manually clean data on modified element + jQuery.removeData(this, 'olddisplay', true); + start(); } @@ -829,9 +841,6 @@ jQuery.makeTest = function( text ){ jQuery("

") .text( text ) .appendTo("#fx-tests") - .click(function(){ - jQuery(this).next().toggle(); - }) .after( elem ); return elem; diff --git a/test/unit/event.js b/test/unit/event.js index cae1a83fe..a6d8cb68b 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1,4 +1,4 @@ -module("event"); +module("event", { teardown: moduleTeardown }); test("null or undefined handler", function() { expect(2); @@ -80,6 +80,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "focusin"; div.trigger("focusin.a"); + // manually clean up detached elements + div.remove(); + div = jQuery("
").bind("click mouseover", obj, function(e) { equals( e.type, cur, "Verify right multi event was fired." ); equals( e.data, obj, "Make sure the data came in correctly." ); @@ -91,6 +94,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "mouseover"; div.trigger("mouseover"); + // manually clean up detached elements + div.remove(); + div = jQuery("
").bind("focusin.a focusout.b", function(e) { equals( e.type, cur, "Verify right multi event was fired." ); }); @@ -100,6 +106,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "focusout"; div.trigger("focusout.b"); + + // manually clean up detached elements + div.remove(); }); test("bind(), namespace with special add", function() { @@ -525,6 +534,9 @@ test("bind(name, false), unbind(name, false)", function() { jQuery("#ap").unbind("click", false); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); + + // manually clean up events from elements outside the fixture + jQuery("#main").unbind("click"); }); test("bind()/trigger()/unbind() on plain object", function() { @@ -663,13 +675,18 @@ test("hover()", function() { test("trigger() shortcuts", function() { expect(6); - jQuery('
  • Change location
  • ').prependTo('#firstUL').find('a').bind('click', function() { + + var elem = jQuery('
  • Change location
  • ').prependTo('#firstUL'); + elem.find('a').bind('click', function() { var close = jQuery('spanx', this); // same with jQuery(this).find('span'); equals( close.length, 0, "Context element does not exist, length must be zero" ); ok( !close[0], "Context element does not exist, direct access to element must return undefined" ); return false; }).click(); + // manually clean up detached elements + elem.remove(); + jQuery("#check1").click(function() { ok( true, "click event handler for checkbox gets fired twice, see #815" ); }).click(); @@ -688,9 +705,12 @@ test("trigger() shortcuts", function() { jQuery('#simon1').click(); equals( clickCounter, 1, "Check that click, triggers onclick event handler on an a tag also" ); - jQuery('').load(function(){ + elem = jQuery('').load(function(){ ok( true, "Trigger the load event, using the shortcut .load() (#2819)"); }).load(); + + // manually clean up detached elements + elem.remove(); }); test("trigger() bubbling", function() { @@ -725,6 +745,10 @@ test("trigger() bubbling", function() { equals( body, 2, "ap bubble" ); equals( main, 1, "ap bubble" ); equals( ap, 1, "ap bubble" ); + + // manually clean up events from elements outside the fixture + jQuery(document).unbind("click"); + jQuery("html, body, #main").unbind("click"); }); test("trigger(type, [data], [fn])", function() { @@ -768,7 +792,7 @@ test("trigger(type, [data], [fn])", function() { pass = true; try { - jQuery('table:first').bind('test:test', function(){}).trigger('test:test'); + jQuery('#main table:first').bind('test:test', function(){}).trigger('test:test'); } catch (e) { pass = false; } @@ -952,6 +976,9 @@ test("toggle(Function, Function, ...)", function() { var data = jQuery._data( $div[0], 'events' ); ok( !data, "Unbinding one function from toggle unbinds them all"); + // manually clean up detached elements + $div.remove(); + // Test Multi-Toggles var a = [], b = []; $div = jQuery("
    "); @@ -967,6 +994,9 @@ test("toggle(Function, Function, ...)", function() { $div.click(); same( a, [1,2,1], "Check that a click worked with a second toggle, second click." ); same( b, [1,2], "Check that a click worked with a second toggle, second click." ); + + // manually clean up detached elements + $div.remove(); }); test(".live()/.die()", function() { @@ -1277,6 +1307,9 @@ test("live with multiple events", function(){ div.trigger("submit"); equals( count, 2, "Make sure both the click and submit were triggered." ); + + // manually clean up events from elements outside the fixture + div.die(); }); test("live with namespaces", function(){ diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index e273cf08a..de841445f 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -1,4 +1,4 @@ -module("manipulation"); +module("manipulation", { teardown: moduleTeardown }); // Ensure that an extended Array prototype doesn't break jQuery Array.prototype.arrayProtoFn = function(arg) { throw("arrayProtoFn should not be called"); }; @@ -115,12 +115,19 @@ var testWrap = function(val) { // Wrap an element with a jQuery set and event result = jQuery("
    ").click(function(){ ok(true, "Event triggered."); + + // Remove handlers on detached elements + result.unbind(); + jQuery(this).unbind(); }); j = jQuery("").wrap(result); equals( j[0].parentNode.nodeName.toLowerCase(), "div", "Wrapping works." ); j.parent().trigger("click"); + + // clean up attached elements + QUnit.reset(); } test("wrap(String|Element)", function() { @@ -408,8 +415,12 @@ test("append the same fragment with events (Bug #6997, 5566)", function () { ok(true, "Event exists on original after being unbound on clone"); jQuery(this).unbind('click'); }); - element.clone(true).unbind('click')[0].fireEvent('onclick'); + var clone = element.clone(true).unbind('click'); + clone[0].fireEvent('onclick'); element[0].fireEvent('onclick'); + + // manually clean up detached elements + clone.remove(); } element = jQuery("").click(function () { @@ -894,20 +905,36 @@ test("clone()", function() { ok( true, "Bound event still exists." ); }); - div = div.clone(true).clone(true); + clone = div.clone(true); + + // manually clean up detached elements + div.remove(); + + div = clone.clone(true); + + // manually clean up detached elements + clone.remove(); + equals( div.length, 1, "One element cloned" ); equals( div[0].nodeName.toUpperCase(), "DIV", "DIV element cloned" ); div.trigger("click"); + // manually clean up detached elements + div.remove(); + div = jQuery("
    ").append([ document.createElement("table"), document.createElement("table") ]); div.find("table").click(function(){ ok( true, "Bound event still exists." ); }); - div = div.clone(true); - equals( div.length, 1, "One element cloned" ); - equals( div[0].nodeName.toUpperCase(), "DIV", "DIV element cloned" ); - div.find("table:last").trigger("click"); + clone = div.clone(true); + equals( clone.length, 1, "One element cloned" ); + equals( clone[0].nodeName.toUpperCase(), "DIV", "DIV element cloned" ); + clone.find("table:last").trigger("click"); + + // manually clean up detached elements + div.remove(); + clone.remove(); // this is technically an invalid object, but because of the special // classid instantiation it is the only kind that IE has trouble with, @@ -928,12 +955,16 @@ test("clone()", function() { equals( clone[0].nodeName.toUpperCase(), "DIV", "DIV element cloned" ); div = jQuery("
    ").data({ a: true }); - var div2 = div.clone(true); - equals( div2.data("a"), true, "Data cloned." ); - div2.data("a", false); - equals( div2.data("a"), false, "Ensure cloned element data object was correctly modified" ); + clone = div.clone(true); + equals( clone.data("a"), true, "Data cloned." ); + clone.data("a", false); + equals( clone.data("a"), false, "Ensure cloned element data object was correctly modified" ); equals( div.data("a"), true, "Ensure cloned element data object is copied, not referenced" ); + // manually clean up detached elements + div.remove(); + clone.remove(); + var form = document.createElement("form"); form.action = "/test/"; var div = document.createElement("div"); @@ -1141,15 +1172,21 @@ var testRemove = function(method) { jQuery("#nonnodes").contents()[method](); equals( jQuery("#nonnodes").contents().length, 0, "Check node,textnode,comment remove works" ); + // manually clean up detached elements + if (method === "detach") { + first.remove(); + } + QUnit.reset(); var count = 0; var first = jQuery("#ap").children(":first"); - var cleanUp = first.click(function() { count++ })[method]().appendTo("body").click(); + var cleanUp = first.click(function() { count++ })[method]().appendTo("#main").click(); equals( method == "remove" ? 0 : 1, count ); - cleanUp.detach(); + // manually clean up detached elements + cleanUp.remove(); }; test("remove()", function() { diff --git a/test/unit/offset.js b/test/unit/offset.js index cfa14449b..329d69f95 100644 --- a/test/unit/offset.js +++ b/test/unit/offset.js @@ -1,4 +1,4 @@ -module("offset"); +module("offset", { teardown: moduleTeardown }); test("disconnected node", function() { expect(2); diff --git a/test/unit/queue.js b/test/unit/queue.js index eada0eede..31e587db2 100644 --- a/test/unit/queue.js +++ b/test/unit/queue.js @@ -1,4 +1,4 @@ -module("queue"); +module("queue", { teardown: moduleTeardown }); test("queue() with other types",function() { expect(9); diff --git a/test/unit/selector.js b/test/unit/selector.js index 5b758c101..c530c1fdb 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -1,4 +1,4 @@ -module("selector"); +module("selector", { teardown: moduleTeardown }); test("element", function() { expect(21); diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 31cffc2eb..f0471d74e 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -1,4 +1,4 @@ -module("traversing"); +module("traversing", { teardown: moduleTeardown }); test("find(String)", function() { expect(5); -- cgit v1.2.3 From 84319aa4a32c1221110419cbe9e427dc9e184555 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Mon, 10 Jan 2011 00:52:20 +0100 Subject: Fixes a race condition in JSONP Local test. --- test/unit/ajax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 773088fc9..9f46c4134 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1084,7 +1084,7 @@ test("jQuery.ajax() - JSONP, Local", function() { expect(10); var count = 0; - function plus(){ if ( ++count == 9 ) start(); } + function plus(){ if ( ++count == 10 ) start(); } stop(); -- cgit v1.2.3 From b14f02899e74c429effadd88527ffde17650149a Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 9 Jan 2011 18:38:44 -0600 Subject: Ensure that buildFragment clones elements properly in all browsers. Fixes #3879, #6655. Also improves form element clone tests and fixes bugs in $.fn.clone exposed by these new test cases related to the values of checkboxes and radio buttons in IE. --- src/core.js | 2 +- src/manipulation.js | 22 ++++++++++++++++++---- test/unit/core.js | 7 ++++++- test/unit/manipulation.js | 32 +++++++++++++++++++++++--------- 4 files changed, 48 insertions(+), 15 deletions(-) (limited to 'test') diff --git a/src/core.js b/src/core.js index b9e6d816f..4361577e2 100644 --- a/src/core.js +++ b/src/core.js @@ -129,7 +129,7 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + selector = (ret.cacheable ? jQuery(ret.fragment).clone()[0] : ret.fragment).childNodes; } return jQuery.merge( this, selector ); diff --git a/src/manipulation.js b/src/manipulation.js index 493082212..cf533c818 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -420,15 +420,29 @@ function cloneFixAttributes(src, dest) { if ( nodeName === "object" ) { dest.outerHTML = src.outerHTML; - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button - } else if ( nodeName === "input" && src.checked ) { - dest.defaultChecked = dest.checked = src.checked; + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } // IE6-8 fails to return the selected option to the default selected // state when cloning options } else if ( nodeName === "option" ) { dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; } // Event data gets referenced instead of copied if the expando diff --git a/test/unit/core.js b/test/unit/core.js index 8fd060578..bfb2f1cf4 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -12,7 +12,7 @@ test("Basic requirements", function() { }); test("jQuery()", function() { - expect(23); + expect(24); // Basic constructor's behavior @@ -84,6 +84,11 @@ test("jQuery()", function() { exec = true; elem.click(); + + for ( var i = 0; i < 3; ++i ) { + elem = jQuery(""); + } + equals( elem[0].defaultValue, "TEST", "Ensure cached nodes are cloned properly (Bug #6655)" ); }); test("selector state", function() { diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index e273cf08a..559a076fa 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -395,7 +395,7 @@ test("append(Function) with incoming value", function() { }); test("append the same fragment with events (Bug #6997, 5566)", function () { - expect(4 + (document.fireEvent ? 1 : 0)); + expect(2 + (document.fireEvent ? 1 : 0)); stop(1000); var element; @@ -426,14 +426,6 @@ test("append the same fragment with events (Bug #6997, 5566)", function () { jQuery("#listWithTabIndex li").before(element); jQuery("#listWithTabIndex li.test6997").eq(1).click(); - - element = jQuery(""); - - equals( element.clone().find("option:selected").val(), element.find("option:selected").val(), "Selected option cloned correctly" ); - - element = jQuery("").attr('checked', 'checked'); - - equals( element.clone().is(":checked"), element.is(":checked"), "Checked input cloned correctly" ); }); test("appendTo(String|Element|Array<Element>|jQuery)", function() { @@ -945,6 +937,28 @@ test("clone()", function() { equal( jQuery("body").clone().children()[0].id, "qunit-header", "Make sure cloning body works" ); }); +test("clone(form element) (Bug #3879, #6655)", function() { + expect(6); + element = jQuery(""); + + equals( element.clone().find("option:selected").val(), element.find("option:selected").val(), "Selected option cloned correctly" ); + + element = jQuery("").attr('checked', 'checked'); + clone = element.clone(); + + equals( clone.is(":checked"), element.is(":checked"), "Checked input cloned correctly" ); + equals( clone[0].defaultValue, "foo", "Checked input defaultValue cloned correctly" ); + equals( clone[0].defaultChecked, !jQuery.support.noCloneEvent, "Checked input defaultChecked cloned correctly" ); + + element = jQuery(""); + clone = element.clone(); + equals( clone[0].defaultValue, "foo", "Text input defaultValue cloned correctly" ); + + element = jQuery(""); + clone = element.clone(); + equals( clone[0].defaultValue, "foo", "Textarea defaultValue cloned correctly" ); +}); + if (!isLocal) { test("clone() on XML nodes", function() { expect(2); -- cgit v1.2.3 From 9bd9d270f69582b119b1835da4419a900f975b6b Mon Sep 17 00:00:00 2001 From: rwldrn Date: Mon, 10 Jan 2011 13:17:08 -0500 Subject: whitespace fixes in unit tests --- test/unit/css.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'test') diff --git a/test/unit/css.js b/test/unit/css.js index edc340ceb..3e65b5131 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -322,23 +322,23 @@ test(":visible selector works properly on children with a hidden parent (bug #45 }); test("internal ref to elem.runtimeStyle (bug #7608)", function () { - expect(1); - - var result = true, - val = 10; - - jQuery('
    ' + - '
     
    ').appendTo("body"); - - try { - // the bug is located within src/css.js - jQuery("#bug7608 #test").animate( { width: val }, 1000); - - } catch (e) { - result = false; - } - - ok( result, "elem.runtimeStyle does not throw exception" ); + expect(1); + + var result = true, + val = 10; + + jQuery('
    ' + + '
     
    ').appendTo("#main"); + + try { + // the bug is located within src/css.js + jQuery("#bug7608 #test").animate( { width: val }, 1000); + + } catch (e) { + result = false; + } + + ok( result, "elem.runtimeStyle does not throw exception" ); - jQuery("#bug7608").remove(); + jQuery("#bug7608").remove(); }); -- cgit v1.2.3 From 5a721cf31b142365954a2d71e860534da4536b16 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Tue, 11 Jan 2011 20:02:33 +0100 Subject: Fixes 4825. jQuery.fn.load: use the jXHR's Promise interface to get the actual response in case ajaxSettings contains a dataFilter. Unit test added. --- src/ajax.js | 18 +++++++++++++----- test/unit/ajax.js | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/src/ajax.js b/src/ajax.js index d570fcccb..82c499b64 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -61,26 +61,34 @@ jQuery.fn.extend({ type: type, dataType: "html", data: params, - complete: function( res, status ) { + // Complete callback (responseText is used internally) + complete: function( jXHR, status, responseText ) { + // Store the response as specified by the jXHR object + responseText = jXHR.responseText; // If successful, inject the HTML into all the matched elements - if ( status === "success" || status === "notmodified" ) { + if ( jXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jXHR.done(function( r ) { + responseText = r; + }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("
    ") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE - .append(res.responseText.replace(rscript, "")) + .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result - res.responseText ); + responseText ); } if ( callback ) { - self.each( callback, [res.responseText, status, res] ); + self.each( callback, [responseText, status, jXHR] ); } } }); diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 9f46c4134..1b5540216 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1025,6 +1025,18 @@ test("load(String, Function) - check file with only a script tag", function() { }); }); +test("load(String, Function) - dataFilter in ajaxSettings", function() { + expect(2); + stop(); + jQuery.ajaxSetup({ dataFilter: function() { return "Hello World"; } }); + var div = jQuery("
    ").load(url("data/name.html"), function(responseText) { + strictEqual( div.html(), "Hello World" , "Test div was filled with filtered data" ); + strictEqual( responseText, "Hello World" , "Test callback receives filtered data" ); + jQuery.ajaxSetup({ dataFilter: 0 }); + start(); + }); +}); + test("load(String, Object, Function)", function() { expect(2); stop(); -- cgit v1.2.3 From 6504d4f7005c99ea4064068f3d712322a064b97a Mon Sep 17 00:00:00 2001 From: jaubourg Date: Tue, 11 Jan 2011 23:01:18 +0100 Subject: Fixes #6230. Added a unit test to control that, since the ajax rewrite, setting the Accept header actually replaced the previous value and didn't append to it (tested in Safari 4.0.4 for which the problem was specifically reported). --- test/unit/ajax.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'test') diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 1b5540216..c7803f857 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -316,6 +316,28 @@ test(".ajax() - headers" , function() { }); +test(".ajax() - Accept header" , function() { + + expect( 1 ); + + stop(); + + jQuery.ajax(url("data/headers.php?keys=accept"), { + headers: { + Accept: "very wrong accept value" + }, + beforeSend: function( xhr ) { + xhr.setRequestHeader( "Accept", "*/*" ); + }, + success: function( data ) { + strictEqual( data , "accept: */*\n" , "Test Accept header is set to last value provided" ); + start(); + }, + error: function(){ ok(false, "error"); } + }); + +}); + test(".ajax() - contentType" , function() { expect( 2 ); -- cgit v1.2.3 From f83cdc3c4c134b2796335e9f9806f3b67e7ca87f Mon Sep 17 00:00:00 2001 From: jaubourg Date: Tue, 11 Jan 2011 23:43:02 +0100 Subject: Yet another missing semicolon! --- test/unit/ajax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/ajax.js b/test/unit/ajax.js index c7803f857..4162d585a 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -276,7 +276,7 @@ test(".ajax() - retry with jQuery.ajax( this )", function() { start(); } } - }) + }); }); -- cgit v1.2.3 From 0c51e9d55f39366cab14719b80cb7e989c716351 Mon Sep 17 00:00:00 2001 From: jaubourg Date: Wed, 12 Jan 2011 18:36:00 +0100 Subject: Fixes #4897. Added ?? as a context-insensitive placeholder for the callback name of a JSONP request. Unit tests provided. --- src/ajax/jsonp.js | 6 +++--- test/data/jsonp.php | 4 ++++ test/unit/ajax.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js index f4b324e17..dd04b34b0 100644 --- a/src/ajax/jsonp.js +++ b/src/ajax/jsonp.js @@ -1,7 +1,7 @@ (function( jQuery ) { var jsc = jQuery.now(), - jsre = /\=(?:\?|%3F)(&|$)/i, + jsre = /(\=)(?:\?|%3F)(&|$)|()(?:\?\?|%3F%3F)()/i, rquery_jsonp = /\?/; // Default jsonp settings @@ -25,8 +25,8 @@ jQuery.ajax.prefilter("json jsonp", function(s, originalSettings) { var jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, - url = s.url.replace(jsre, "=" + jsonpCallback + "$1"), - data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "=" + jsonpCallback + "$1") : s.data; + url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"), + data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data; if ( url === s.url && data === s.data ) { url += (rquery_jsonp.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; diff --git a/test/data/jsonp.php b/test/data/jsonp.php index 9ae1d8487..6c13d72e9 100644 --- a/test/data/jsonp.php +++ b/test/data/jsonp.php @@ -1,6 +1,10 @@ Date: Thu, 13 Jan 2011 17:01:25 +0100 Subject: Fixes #4964. Adds a statusCode object together with a new statusCode method on the jXHR object (deferred behaviour). They accept a map of statusCode/callback(s). Callbacks are fired when the status code of the response correponds to the key (as a success or an error callback depending on how the request completed). Unit tests added. --- src/ajax.js | 27 +++++++++++++++++++++ test/unit/ajax.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) (limited to 'test') diff --git a/src/ajax.js b/src/ajax.js index c2dc3e404..a2ef4397b 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -265,6 +265,8 @@ jQuery.extend({ // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery._Deferred(), + // Status-dependent callbacks + statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, // Response headers @@ -520,6 +522,9 @@ jQuery.extend({ deferred.fireReject( callbackContext , [ jXHR , statusText , error ] ); } + // Status-dependent callbacks + jXHR.statusCode( statusCode ); + if ( s.global ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , [ jXHR , s , isSuccess ? success : error ] ); @@ -543,6 +548,28 @@ jQuery.extend({ jXHR.error = jXHR.fail; jXHR.complete = completeDeferred.done; + // Status-dependent callbacks + jXHR.statusCode = function( map ) { + if ( map ) { + var resolved = jXHR.isResolved(), + tmp; + if ( resolved || jXHR.isRejected() ) { + tmp = map[ jXHR.status ]; + if ( tmp ) { + if ( map === statusCode ) { + delete statusCode[ jXHR.status ]; + } + jXHR[ resolved ? "done" : "fail" ]( tmp ); + } + } else { + for( tmp in map ) { + statusCode[ tmp ] = [ statusCode[ tmp ] , map[ tmp ] ]; + } + } + } + return this; + }; + // Remove hash character (#7531: and string promotion) s.url = ( "" + s.url ).replace( rhash , "" ); diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 10c2e7183..f5b71da39 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1915,6 +1915,76 @@ test( "jQuery.ajax - Location object as url (#7531)", 1, function () { ok( success, "document.location did not generate exception" ); }); +test( "jQuery.ajax - statusCode" , function() { + + var count = 10; + + expect( 16 ); + stop(); + + function countComplete() { + if ( ! --count ) { + start(); + } + } + + function createStatusCodes( name , isSuccess ) { + name = "Test " + name + " " + ( isSuccess ? "success" : "error" ); + return { + 200: function() { + ok( isSuccess , name ); + }, + 404: function() { + ok( ! isSuccess , name ); + } + } + } + + jQuery.each( { + "data/name.html": true, + "data/someFileThatDoesNotExist.html": false + } , function( uri , isSuccess ) { + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "in options" , isSuccess ), + complete: countComplete + }); + + jQuery.ajax( url( uri ) , { + complete: countComplete + }).statusCode( createStatusCodes( "immediately with method" , isSuccess ) ); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "on complete" , isSuccess ) ); + countComplete(); + } + }); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "very late binding" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }); + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "all (options)" , isSuccess ), + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "all (on complete)" , isSuccess ) ); + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "all (very late binding)" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }).statusCode( createStatusCodes( "all (immediately with method)" , isSuccess ) ); + + }); + +}); + } //} \ No newline at end of file -- cgit v1.2.3 From d483ce0a9c8b19dafa70be93ca071673b86a65e2 Mon Sep 17 00:00:00 2001 From: Jared Grippe Date: Sat, 9 Oct 2010 17:32:54 -0700 Subject: added jQuery.subclass --- src/core.js | 31 ++++++++++++++++++++---- test/unit/core.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/src/core.js b/src/core.js index 4361577e2..0bf0cb4b7 100644 --- a/src/core.js +++ b/src/core.js @@ -3,7 +3,7 @@ var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); + return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite @@ -78,7 +78,8 @@ var jQuery = function( selector, context ) { class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) @@ -112,6 +113,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag @@ -171,7 +173,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { - return jQuery( context ).find( selector ); + return this.constructor( context ).find( selector ); } // HANDLE: $(function) @@ -222,7 +224,7 @@ jQuery.fn = jQuery.prototype = { // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set - var ret = jQuery(); + var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); @@ -287,7 +289,7 @@ jQuery.fn = jQuery.prototype = { }, end: function() { - return this.prevObject || jQuery(null); + return this.prevObject || this.constructor(null); }, // For internal use only. @@ -960,6 +962,25 @@ jQuery.extend({ return { browser: match[1] || "", version: match[2] || "0" }; }, + subclass: function(){ + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if (context && context instanceof jQuery && !(context instanceof jQuerySubclass)){ + context = jQuerySubclass(context); + } + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + browser: {} }); diff --git a/test/unit/core.js b/test/unit/core.js index bfb2f1cf4..d26e7c56d 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1063,3 +1063,75 @@ test("jQuery.when()", function() { }); } }); + +test("jQuery.subclass", function(){ + expect(378); + + var Subclass = jQuery.subclass(), + SubclassSubclass = Subclass.subclass(), + jQueryDocument = jQuery(document), + selectors, contexts, methods, method, arg, description; + + jQueryDocument.toString = function(){ return 'jQueryDocument'; }; + + Subclass.fn.subclassMethod = function(){}; + SubclassSubclass.fn.subclassSubclassMethod = function(){}; + + selectors = [ + 'body', + 'html, body', + '
    ' + ] + + methods = [ // all methods that return a new jQuery instance + ['eq', 1], + ['add', document], + ['end'], + ['has'], + ['closest', 'div'], + ['filter', document], + ['find'] + ] + + contexts = [undefined, document, jQueryDocument]; + + jQuery.each(selectors, function(i, selector){ + + jQuery.each(methods, function(){ + method = this[0] + arg = this[1] + + jQuery.each(contexts, function(i, context){ + + description = '("'+selector+'", '+context+').'+method+'('+(arg||'')+')'; + + same( + jQuery(selector, context)[method](arg).subclassMethod, undefined, + 'jQuery'+description+' doesnt have Subclass methods' + ); + same( + jQuery(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'jQuery'+description+' doesnt have SubclassSubclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'Subclass'+description+' has Subclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'Subclass'+description+' doesnt have SubclassSubclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'SubclassSubclass'+description+' has Subclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassSubclassMethod, SubclassSubclass.fn.subclassSubclassMethod, + 'SubclassSubclass'+description+' has SubclassSubclass methods' + ); + + }); + }); + }); + +}); -- cgit v1.2.3 From 3716f2d0416895e1220269ea2e8bb23b8b843db0 Mon Sep 17 00:00:00 2001 From: wycats Date: Fri, 14 Jan 2011 10:36:45 -0500 Subject: Overwrite CRLF with LF --- test/delegatetest.html | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'test') diff --git a/test/delegatetest.html b/test/delegatetest.html index 6479d26ec..efc206814 100644 --- a/test/delegatetest.html +++ b/test/delegatetest.html @@ -229,14 +229,22 @@ $(document).bind("change", function(){ jQuery("#boundChange").blink(); }); +<<<<<<< HEAD +======= + +>>>>>>> Overwrite CRLF with LF $("#text_submit").addSubmitTest("#textSubmit", true); $("#password_submit").addSubmitTest("#passwordSubmit", true); $("#submit_submit").addSubmitTest("#submitSubmit", true); $(document).bind("submit", function(){ jQuery("#boundSubmit").blink(); }); +<<<<<<< HEAD +======= + +>>>>>>> Overwrite CRLF with LF -- cgit v1.2.3 From fa45f25d1c554663f696c43bb1d08d24fec6abf5 Mon Sep 17 00:00:00 2001 From: wycats Date: Fri, 14 Jan 2011 11:17:32 -0500 Subject: Fix a failing subclass test due to a change in master --- test/unit/core.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/core.js b/test/unit/core.js index d26e7c56d..db160b2d6 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1081,7 +1081,7 @@ test("jQuery.subclass", function(){ 'body', 'html, body', '
    ' - ] + ]; methods = [ // all methods that return a new jQuery instance ['eq', 1], @@ -1090,16 +1090,16 @@ test("jQuery.subclass", function(){ ['has'], ['closest', 'div'], ['filter', document], - ['find'] - ] + ['find', 'div'] + ]; contexts = [undefined, document, jQueryDocument]; jQuery.each(selectors, function(i, selector){ jQuery.each(methods, function(){ - method = this[0] - arg = this[1] + method = this[0]; + arg = this[1]; jQuery.each(contexts, function(i, context){ -- cgit v1.2.3 From f4add19759678797ed8078a96d0c18b168df8588 Mon Sep 17 00:00:00 2001 From: wycats Date: Fri, 14 Jan 2011 12:46:14 -0500 Subject: So embarrassing :( --- test/delegatetest.html | 8 -------- 1 file changed, 8 deletions(-) (limited to 'test') diff --git a/test/delegatetest.html b/test/delegatetest.html index efc206814..6479d26ec 100644 --- a/test/delegatetest.html +++ b/test/delegatetest.html @@ -229,22 +229,14 @@ $(document).bind("change", function(){ jQuery("#boundChange").blink(); }); -<<<<<<< HEAD -======= - ->>>>>>> Overwrite CRLF with LF $("#text_submit").addSubmitTest("#textSubmit", true); $("#password_submit").addSubmitTest("#passwordSubmit", true); $("#submit_submit").addSubmitTest("#submitSubmit", true); $(document).bind("submit", function(){ jQuery("#boundSubmit").blink(); }); -<<<<<<< HEAD -======= - ->>>>>>> Overwrite CRLF with LF -- cgit v1.2.3 From 007e2d152e10a8feffa347b5f328d2cb4bd45327 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Sat, 15 Jan 2011 10:24:13 -0500 Subject: Update test case to feature-detect Opera's lack of defaultPrevented and skip the test for it. --- test/unit/event.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/event.js b/test/unit/event.js index 56bc64ff6..d5d6790d4 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -314,7 +314,14 @@ test("bind/delegate bubbling, isDefaultPrevented", function() { e.preventDefault(); }); $main.delegate("#foo", "click", function(e) { - equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + var orig = e.originalEvent; + if ( typeof(orig.defaultPrevented) === "boolean" || typeof(orig.returnValue) === "boolean" || orig.getPreventDefault ) { + equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + } + else { + // Opera < 11 doesn't implement any interface we can use, so give it a pass + ok( true, "isDefaultPrevented not supported by this browser, test skipped" ); + } }); fakeClick( $anchor2 ); $anchor2.unbind( "click" ); -- cgit v1.2.3 From 8ab23aec2c333834a6e442fa15b73125ba857afe Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 02:57:39 +0100 Subject: Fixes #2994. Not finding a transport now fires the error callbacks and doesn't make ajax return false. Had to revise how jsonp and script prefilters & transports work (better separation of concerns). Also took the opportunity to revise jXHR getRequestHeader and abort methods and enabled early transport garbage collection when the request completes. --- src/ajax.js | 142 +++++++++++++++++++++++++++++------------------------ src/ajax/jsonp.js | 71 ++++++++++++--------------- src/ajax/script.js | 16 +++--- test/unit/ajax.js | 16 ++---- 4 files changed, 123 insertions(+), 122 deletions(-) (limited to 'test') diff --git a/src/ajax.js b/src/ajax.js index 645163ad2..5c4d469fd 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -306,30 +306,35 @@ jQuery.extend({ // (match is used internally) getResponseHeader: function( key , match ) { - if ( state !== 2 ) { - return null; - } + if ( state === 2 ) { - if ( responseHeaders === undefined ) { + if ( responseHeaders === undefined ) { - responseHeaders = {}; + responseHeaders = {}; - if ( typeof responseHeadersString === "string" ) { + if ( typeof responseHeadersString === "string" ) { - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } } } + match = responseHeaders[ key.toLowerCase() ]; + + } else { + + match = null; } - return responseHeaders[ key.toLowerCase() ]; + + return match; }, // Cancel the request abort: function( statusText ) { - if ( transport && state !== 2 ) { + if ( transport ) { transport.abort( statusText || "abort" ); - done( 0 , statusText ); } + done( 0 , statusText ); return this; } }; @@ -347,6 +352,10 @@ jQuery.extend({ // State is "done" now state = 2; + // Dereference transport for early garbage collection + // (no matter how long the jXHR transport will be used + transport = 0; + // Set readyState jXHR.readyState = status ? 4 : 0; @@ -599,84 +608,87 @@ jQuery.extend({ s.data = jQuery.param( s.data , s.traditional ); } - // Get transport - transport = jQuery.ajaxPrefilter( s , options ).ajaxTransport( s ); + // Apply prefilters + jQuery.ajaxPrefilter( s , options ); // Watch for a new set of requests if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } - // If no transport, we auto-abort - if ( ! transport ) { - - done( 0 , "transport not found" ); - jXHR = false; + // More options handling for requests with no content + if ( ! s.hasContent ) { - } else { + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + } - // More options handling for requests with no content - if ( ! s.hasContent ) { + // Add anti-cache in url if needed + if ( s.cache === false ) { - // If data is available, append data to url - if ( s.data ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; - } + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts , "$1_=" + ts ); - // Add anti-cache in url if needed - if ( s.cache === false ) { + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); + } + } - var ts = jQuery.now(), - // try replacing _= if it is there - ret = s.url.replace( rts , "$1_=" + ts ); + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } - // if nothing was replaced, add timestamp to the end - s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); - } + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery_lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - requestHeaders[ "content-type" ] = s.contentType; + if ( jQuery_etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; } + } - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery_lastModified[ s.url ] ) { - requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; - } - if ( jQuery_etag[ s.url ] ) { - requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; - } - } + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + } - // Set the Accepts header for the server, depending on the dataType - requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : - s.accepts[ "*" ]; + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { - // Check for headers option - for ( i in s.headers ) { - requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; + // Abort if not done already + done( 0 , "abort" ); + + // Return false + jXHR = false; + + } else { + + // Install callbacks on deferreds + for ( i in { success:1, error:1, complete:1 } ) { + jXHR[ i ]( s[ i ] ); } - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { + // Get transport + transport = jQuery.ajaxTransport( s ); - // Abort if not done already - done( 0 , "abort" ); - jXHR = false; + // If no transport, we auto-abort + if ( ! transport ) { + + done( 0 , "notransport" ); } else { // Set state as sending - state = 1; - jXHR.readyState = 1; - - // Install callbacks on deferreds - for ( i in { success:1, error:1, complete:1 } ) { - jXHR[ i ]( s[ i ] ); - } + state = jXHR.readyState = 1; // Send global event if ( s.global ) { diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js index 1df5dd427..675ecc085 100644 --- a/src/ajax/jsonp.js +++ b/src/ajax/jsonp.js @@ -11,9 +11,7 @@ jQuery.ajaxSetup({ return "jsonp" + jsc++; } -// Normalize jsonp queries -// 1) put callback parameter in url or data -// 2) sneakily ensure transportDataType is always jsonp for jsonp requests +// Detect, normalize options and install callbacks for jsonp requests }).ajaxPrefilter("json jsonp", function(s, originalSettings) { if ( s.dataTypes[ 0 ] === "jsonp" || @@ -22,8 +20,10 @@ jQuery.ajaxSetup({ jsre.test(s.url) || typeof(s.data) === "string" && jsre.test(s.data) ) { - var jsonpCallback = s.jsonpCallback = + var responseContainer, + jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], url = s.url.replace(jsre, "$1" + jsonpCallback + "$2"), data = s.url === url && typeof(s.data) === "string" ? s.data.replace(jsre, "$1" + jsonpCallback + "$2") : s.data; @@ -33,51 +33,42 @@ jQuery.ajaxSetup({ s.url = url; s.data = data; - s.dataTypes[ 0 ] = "jsonp"; - } - -// Bind transport to jsonp dataType -}).ajaxTransport("jsonp", function(s) { - // Put callback in place - var responseContainer, - jsonpCallback = s.jsonpCallback, - previous = window[ jsonpCallback ]; + window [ jsonpCallback ] = function( response ) { + responseContainer = [response]; + }; - window [ jsonpCallback ] = function( response ) { - responseContainer = [response]; - }; + s.complete = [function() { - s.complete = [function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; - // Set callback back to previous value - window[ jsonpCallback ] = previous; - - // Call if it was a function and we have a response - if ( previous) { - if ( responseContainer && jQuery.isFunction ( previous ) ) { - window[ jsonpCallback ] ( responseContainer[0] ); + // Call if it was a function and we have a response + if ( previous) { + if ( responseContainer && jQuery.isFunction ( previous ) ) { + window[ jsonpCallback ] ( responseContainer[0] ); + } + } else { + // else, more memory leak avoidance + try{ delete window[ jsonpCallback ]; } catch(e){} } - } else { - // else, more memory leak avoidance - try{ delete window[ jsonpCallback ]; } catch(e){} - } - }, s.complete ]; + }, s.complete ]; - // Sneakily ensure this will be handled as json - s.dataTypes[ 0 ] = "json"; + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( ! responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( ! responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); - } - return responseContainer[ 0 ]; - }; + // force json dataType + s.dataTypes[ 0 ] = "json"; - // Delegate to script transport - return "script"; + // Delegate to script + return "script"; + } }); })( jQuery ); diff --git a/src/ajax/script.js b/src/ajax/script.js index 8e2e89ac5..ee1d489eb 100644 --- a/src/ajax/script.js +++ b/src/ajax/script.js @@ -15,18 +15,22 @@ jQuery.ajaxSetup({ "text script": jQuery.globalEval } -// Bind script tag hack transport -}).ajaxTransport("script", function(s) { +// Handle cache's special case and global +}).ajaxPrefilter("script", function(s) { - // Handle cache special case if ( s.cache === undefined ) { s.cache = false; } - // This transport only deals with cross domain get requests - if ( s.crossDomain && s.async && ( s.type === "GET" || ! s.data ) ) { - + if ( s.crossDomain ) { s.global = false; + } + +// Bind script tag hack transport +}).ajaxTransport("script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { var script, head = document.getElementsByTagName("head")[0] || document.documentElement; diff --git a/test/unit/ajax.js b/test/unit/ajax.js index f5b71da39..49196cbbb 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1865,25 +1865,19 @@ test("jQuery ajax - failing cross-domain", function() { var i = 2; - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); - if ( jQuery.ajax({ + jQuery.ajax({ url: 'http://www.google.com', success: function(){ ok( false , "success" ); }, error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, complete: function() { if ( ! --i ) start(); } - }) === false ) { - ok( true , "no transport" ); - if ( ! --i ) start(); - } + }); }); @@ -1937,7 +1931,7 @@ test( "jQuery.ajax - statusCode" , function() { 404: function() { ok( ! isSuccess , name ); } - } + }; } jQuery.each( { -- cgit v1.2.3 From 0e93b2e24c67237c1172b5d7b1f75f52904d6a1c Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 05:27:16 +0100 Subject: Revised jsonp unit tests and added a test for when the jsonp option is set to false. --- test/unit/ajax.js | 481 +++++++++++++++++++++++++----------------------------- 1 file changed, 222 insertions(+), 259 deletions(-) (limited to 'test') diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 49196cbbb..2e8a455eb 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1114,286 +1114,249 @@ test("jQuery.getScript(String, Function) - no callback", function() { }); }); -test("jQuery.ajax() - JSONP, Local", function() { - expect(14); - - var count = 0; - function plus(){ if ( ++count == 14 ) start(); } +jQuery.each( [ "Same Domain", "Cross Domain" ] , function( crossDomain , label ) { - stop(); + test("jQuery.ajax() - JSONP, " + label, function() { + expect(16); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); - - jQuery.ajax({ - url: "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); - - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + var count = 0; + function plus(){ if ( ++count == 16 ) start(); } - jQuery.ajax({ - url: "data/jsonp.php?callback=??", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url context-free callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url context-free callback)" ); - plus(); - } - }); + stop(); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: "callback=??", - success: function(data){ - ok( data.data, "JSON results returned (GET, data context-free callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data context-free callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, no callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php/??", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, REST-like)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, REST-like)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php/???json=1", - dataType: "jsonp", - success: function(data){ - strictEqual( jQuery.type(data), "array", "JSON results returned (GET, REST-like with param)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, REST-like with param)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=?", + success: function(data){ + ok( data.data, "JSON results returned (GET, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: { - callback: "?" - }, - success: function(data){ - ok( data.data, "JSON results returned (GET, processed data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, processed data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url context-free callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=??", + success: function(data){ + ok( data.data, "JSON results returned (GET, data context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data context-free callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults", - success: function(data){ - ok( data.data, "JSON results returned (GET, custom callback name)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, custom callback name)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php/??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, REST-like)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php/???json=1", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + strictEqual( jQuery.type(data), "array", "JSON results returned (GET, REST-like with param)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like with param)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - data: "callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: { + callback: "?" + }, + success: function(data){ + ok( data.data, "JSON results returned (GET, processed data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, processed data callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - jsonp: "callback", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonp: "callback", + success: function(data){ + ok( data.data, "JSON results returned (GET, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); - //#7578 - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - beforeSend: function(){ - strictEqual( this.cache, false, "cache must be false on JSON request" ); + window.jsonpResults = function(data) { + ok( data.data, "JSON results returned (GET, custom callback function)" ); + window.jsonpResults = undefined; plus(); - return false; - } - }); -}); - -test("jQuery.ajax() - JSONP - Custom JSONP Callback", function() { - expect(1); - stop(); - - window.jsonpResults = function(data) { - ok( data.data, "JSON results returned (GET, custom callback function)" ); - window.jsonpResults = undefined; - start(); - }; - - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults" - }); -}); - -test("jQuery.ajax() - JSONP, Remote", function() { - expect(4); + }; - var count = 0; - function plus(){ if ( ++count == 4 ) start(); } + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "jsonpResults", + success: function(data){ + ok( data.data, "JSON results returned (GET, custom callback name)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, custom callback name)" ); + plus(); + } + }); - var base = window.location.href.replace(/[^\/]*$/, ""); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); - stop(); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + data: "callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + jsonp: "callback", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data obj callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); + //#7578 + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function(){ + strictEqual( this.cache, false, "cache must be false on JSON request" ); + plus(); + return false; + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=XXX", + dataType: "jsonp", + jsonp: false, + jsonpCallback: "XXX", + crossDomain: crossDomain, + beforeSend: function() { + console.log( this.url ); + }, + success: function(data){ + console.log(data); + ok( data.data, "JSON results returned (GET, custom callback name with no url manipulation)" ); + plus(); + }, + error: function(data){ + console.log(data); + ok( false, "Ajax error JSON (GET, custom callback name with no url manipulation)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } }); }); -- cgit v1.2.3 From cfca6d3836476a233cf349abc5f433f1ba7b8feb Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 05:36:20 +0100 Subject: Fixed the ajax test regarding the jsonp option set to false and added a test to control the prefilter actually does not tamper with the url. --- test/unit/ajax.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 2e8a455eb..f26ebadee 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1117,10 +1117,10 @@ test("jQuery.getScript(String, Function) - no callback", function() { jQuery.each( [ "Same Domain", "Cross Domain" ] , function( crossDomain , label ) { test("jQuery.ajax() - JSONP, " + label, function() { - expect(16); + expect(17); var count = 0; - function plus(){ if ( ++count == 16 ) start(); } + function plus(){ if ( ++count == 17 ) start(); } stop(); @@ -1343,15 +1343,15 @@ jQuery.each( [ "Same Domain", "Cross Domain" ] , function( crossDomain , label ) jsonpCallback: "XXX", crossDomain: crossDomain, beforeSend: function() { - console.log( this.url ); + ok( /^data\/jsonp.php\?callback=XXX&_=\d+$/.test( this.url ) , + "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + plus(); }, success: function(data){ - console.log(data); ok( data.data, "JSON results returned (GET, custom callback name with no url manipulation)" ); plus(); }, error: function(data){ - console.log(data); ok( false, "Ajax error JSON (GET, custom callback name with no url manipulation)" ); plus(); } -- cgit v1.2.3 From c272f5f7da3473fd5ac85efe783a0d63608ec62b Mon Sep 17 00:00:00 2001 From: jaubourg Date: Sun, 16 Jan 2011 17:41:39 +0100 Subject: Implements joined jQuery.when statements. Makes it so calling jQuery.when with no parameter returns a resolved promise. Ensures promise method on promises supports the promise(obj) signature. Ensures a deferred and its promise always return the same promise (itself for the promise). Unit tests provided. --- src/core.js | 47 ++++++++++++++++++++++++++++++++++++----------- test/unit/core.js | 46 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/src/core.js b/src/core.js index 0bf0cb4b7..25ef22d76 100644 --- a/src/core.js +++ b/src/core.js @@ -898,9 +898,10 @@ jQuery.extend({ Deferred: function( func ) { var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(); + failDeferred = jQuery._Deferred(), + promise; - // Add errorDeferred methods and redefine cancel + // Add errorDeferred methods, then and promise jQuery.extend( deferred , { then: function( doneCallbacks , failCallbacks ) { @@ -914,13 +915,15 @@ jQuery.extend({ // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { - obj = obj || {}; - jQuery.each( "then done fail isResolved isRejected".split( " " ) , function( _ , method ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + jQuery.each( "then done fail isResolved isRejected promise".split( " " ) , function( _ , method ) { obj[ method ] = deferred[ method ]; }); - obj.promise = function() { - return obj; - }; return obj; } @@ -942,10 +945,32 @@ jQuery.extend({ // Deferred helper when: function( object ) { - object = object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred().resolve( object ); - return object.promise(); + var args = arguments, + length = args.length, + deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(), + resolveArray; + + if ( length > 1 ) { + resolveArray = new Array( length ); + jQuery.each( args, function( index, element, args ) { + jQuery.when( element ).done( function( value ) { + args = arguments; + resolveArray[ index ] = args.length > 1 ? slice.call( args , 0 ) : value; + if( ! --length ) { + deferred.fire( promise, resolveArray ); + } + }).fail( function() { + deferred.fireReject( promise, arguments ); + }); + return !deferred.isRejected(); + }); + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; }, // Use of jQuery.browser is frowned upon. diff --git a/test/unit/core.js b/test/unit/core.js index db160b2d6..8e3756a9c 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -21,7 +21,7 @@ test("jQuery()", function() { equals( jQuery(null).length, 0, "jQuery(null) === jQuery([])" ); equals( jQuery("").length, 0, "jQuery('') === jQuery([])" ); - var obj = jQuery("div") + var obj = jQuery("div"); equals( jQuery(obj).selector, "div", "jQuery(jQueryObj) == jQueryObj" ); // can actually yield more than one, when iframes are included, the window is an array as well @@ -1003,7 +1003,7 @@ test("jQuery._Deferred()", function() { test("jQuery.Deferred()", function() { - expect( 4 ); + expect( 6 ); jQuery.Deferred( function( defer ) { strictEqual( this , defer , "Defer passed as this & first argument" ); @@ -1023,11 +1023,16 @@ test("jQuery.Deferred()", function() { }, function() { ok( true , "Error on reject" ); }); + + var tmp = jQuery.Deferred(); + + strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); }); test("jQuery.when()", function() { - expect( 21 ); + expect( 23 ); // Some other objects jQuery.each( { @@ -1050,6 +1055,10 @@ test("jQuery.when()", function() { } ); + ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); + } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + var cache, i; for( i = 1 ; i < 4 ; i++ ) { @@ -1064,6 +1073,37 @@ test("jQuery.when()", function() { } }); +test("jQuery.when() - joined", function() { + + expect(8); + + jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); + strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); + strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - non-observables"); + }); + + var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), + errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); + + jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); + same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); + strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - resolved observable"); + }); + + jQuery.when( 1 , errorDeferred , 3 ).done( function() { + ok( false , "Test the created deferred was rejected - rejected observable"); + }).fail( function( error , errorParam ) { + strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); + strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); + }); +}); + test("jQuery.subclass", function(){ expect(378); -- cgit v1.2.3 From ac2e731b2d219fd516a166a058831de1ff949d4a Mon Sep 17 00:00:00 2001 From: jaubourg Date: Mon, 17 Jan 2011 17:52:30 +0100 Subject: Added unit tests to check and demonstrate new jQuery.Deferred() works. --- test/unit/core.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/core.js b/test/unit/core.js index 8e3756a9c..88f7609f2 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1003,7 +1003,7 @@ test("jQuery._Deferred()", function() { test("jQuery.Deferred()", function() { - expect( 6 ); + expect( 10 ); jQuery.Deferred( function( defer ) { strictEqual( this , defer , "Defer passed as this & first argument" ); @@ -1024,6 +1024,25 @@ test("jQuery.Deferred()", function() { ok( true , "Error on reject" ); }); + ( new jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument (new)" ); + this.resolve( "done" ); + }) ).then( function( value ) { + strictEqual( value , "done" , "Passed function executed (new)" ); + }); + + ( new jQuery.Deferred() ).resolve().then( function() { + ok( true , "Success on resolve (new)" ); + }, function() { + ok( false , "Error on resolve (new)" ); + }); + + ( new jQuery.Deferred() ).reject().then( function() { + ok( false , "Success on reject (new)" ); + }, function() { + ok( true , "Error on reject (new)" ); + }); + var tmp = jQuery.Deferred(); strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); -- cgit v1.2.3 From 0c1ffe3cb381430ec501385fcb29dca22a27d816 Mon Sep 17 00:00:00 2001 From: jeresig Date: Mon, 17 Jan 2011 17:44:42 -0500 Subject: Fixed a couple issues with escaping of attribute values in selectors. Fixes #6093. --- test/unit/selector.js | 67 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) (limited to 'test') diff --git a/test/unit/selector.js b/test/unit/selector.js index 5b758c101..27ad6e535 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -58,13 +58,14 @@ if ( location.protocol != "file:" ) { } test("broken", function() { - expect(8); + expect(20); + function broken(name, selector) { try { jQuery(selector); ok( false, name + ": " + selector ); } catch(e){ - ok( typeof e === "string" && e.indexOf("Syntax error") >= 0, + ok( typeof e === "string" && e.indexOf("Syntax error") >= 0, name + ": " + selector ); } } @@ -77,10 +78,28 @@ test("broken", function() { broken( "Broken Selector", "<>", [] ); broken( "Broken Selector", "{}", [] ); broken( "Doesn't exist", ":visble", [] ); + broken( "Nth-child", ":nth-child", [] ); + broken( "Nth-child", ":nth-child(-)", [] ); + broken( "Nth-child", ":nth-child(asdf)", [] ); + broken( "Nth-child", ":nth-child(2n+-0)", [] ); + broken( "Nth-child", ":nth-child(2+0)", [] ); + broken( "Nth-child", ":nth-child(- 1n)", [] ); + broken( "Nth-child", ":nth-child(-1 n)", [] ); + broken( "First-child", ":first-child(n)", [] ); + broken( "Last-child", ":last-child(n)", [] ); + broken( "Only-child", ":only-child(n)", [] ); + + // Make sure attribute value quoting works correctly. See: #6093 + var attrbad = jQuery('').appendTo("body"); + + broken( "Attribute not escaped", "input[name=foo.baz]", [] ); + broken( "Attribute not escaped", "input[name=foo[baz]]", [] ); + + attrbad.remove(); }); test("id", function() { - expect(28); + expect(29); t( "ID Selector", "#body", ["body"] ); t( "ID Selector w/ Element", "body#body", ["body"] ); t( "ID Selector w/ Element", "ul#first", [] ); @@ -116,6 +135,9 @@ test("id", function() { same( jQuery("body").find("div#form").get(), [], "ID selector within the context of another element" ); + //#7533 + equal( jQuery("

    foo

    ").find("p").length, 1, "Find where context root is a node and has an ID with CSS3 meta characters" ); + t( "Underscore ID", "#types_all", ["types_all"] ); t( "Dash ID", "#fx-queue", ["fx-queue"] ); @@ -150,7 +172,7 @@ test("class", function() { t( "Child escaped Class", "form > .test\\.foo\\[5\\]bar", ["test.foo[5]bar"] ); var div = document.createElement("div"); - div.innerHTML = "
    "; + div.innerHTML = "
    "; same( jQuery(".e", div).get(), [ div.firstChild ], "Finding a second class." ); div.lastChild.className = "e"; @@ -202,7 +224,7 @@ test("multiple", function() { }); test("child and adjacent", function() { - expect(27); + expect(31); t( "Child", "p > a", ["simon1","google","groups","mark","yahoo","simon"] ); t( "Child", "p> a", ["simon1","google","groups","mark","yahoo","simon"] ); t( "Child", "p >a", ["simon1","google","groups","mark","yahoo","simon"] ); @@ -210,20 +232,25 @@ test("child and adjacent", function() { t( "Child w/ Class", "p > a.blog", ["mark","simon"] ); t( "All Children", "code > *", ["anchor1","anchor2"] ); t( "All Grandchildren", "p > * > *", ["anchor1","anchor2"] ); - t( "Adjacent", "#main a + a", ["groups"] ); - t( "Adjacent", "#main a +a", ["groups"] ); - t( "Adjacent", "#main a+ a", ["groups"] ); - t( "Adjacent", "#main a+a", ["groups"] ); + t( "Adjacent", "a + a", ["groups"] ); + t( "Adjacent", "a +a", ["groups"] ); + t( "Adjacent", "a+ a", ["groups"] ); + t( "Adjacent", "a+a", ["groups"] ); t( "Adjacent", "p + p", ["ap","en","sap"] ); t( "Adjacent", "p#firstp + p", ["ap"] ); t( "Adjacent", "p[lang=en] + p", ["sap"] ); t( "Adjacent", "a.GROUPS + code + a", ["mark"] ); - t( "Comma, Child, and Adjacent", "#main a + a, code > a", ["groups","anchor1","anchor2"] ); + t( "Comma, Child, and Adjacent", "a + a, code > a", ["groups","anchor1","anchor2"] ); t( "Element Preceded By", "p ~ div", ["foo", "moretests","tabindex-tests", "liveHandlerOrder", "siblingTest"] ); t( "Element Preceded By", "#first ~ div", ["moretests","tabindex-tests", "liveHandlerOrder", "siblingTest"] ); t( "Element Preceded By", "#groups ~ a", ["mark"] ); t( "Element Preceded By", "#length ~ input", ["idTest"] ); t( "Element Preceded By", "#siblingfirst ~ em", ["siblingnext"] ); + same( jQuery("#siblingfirst").find("~ em").get(), q("siblingnext"), "Element Preceded By with a context." ); + same( jQuery("#siblingfirst").find("+ em").get(), q("siblingnext"), "Element Directly Preceded By with a context." ); + var a = jQuery("

    "); + same( jQuery("~ p", a[0]).get(), [a[1], a[2]], "Detached Element Directly Preceded By with a context." ); + same( jQuery("+ p", a[0]).get(), [a[1]], "Detached Element Preceded By with a context." ); t( "Verify deep class selector", "div.blah > p > a", [] ); @@ -237,7 +264,8 @@ test("child and adjacent", function() { }); test("attributes", function() { - expect(39); + expect(41); + t( "Attribute Exists", "a[title]", ["google"] ); t( "Attribute Exists", "*[title]", ["google"] ); t( "Attribute Exists", "[title]", ["google"] ); @@ -294,10 +322,18 @@ test("attributes", function() { t("Select options via :selected", "#select3 option:selected", ["option3b", "option3c"] ); t( "Grouped Form Elements", "input[name='foo[bar]']", ["hidden2"] ); + + // Make sure attribute value quoting works correctly. See: #6093 + var attrbad = jQuery('').appendTo("body"); + + t("Find escaped attribute value", "input[name=foo\\.baz]", ["attrbad1"]); + t("Find escaped attribute value", "input[name=foo\\[baz\\]]", ["attrbad2"]); + + attrbad.remove(); }); test("pseudo - child", function() { - expect(31); + expect(38); t( "First Child", "p:first-child", ["firstp","sndp"] ); t( "Last Child", "p:last-child", ["sap"] ); t( "Only Child", "#main a:only-child", ["simon1","anchor1","yahoo","anchor2","liveLink1","liveLink2"] ); @@ -306,6 +342,7 @@ test("pseudo - child", function() { t( "First Child", "p:first-child", ["firstp","sndp"] ); t( "Nth Child", "p:nth-child(1)", ["firstp","sndp"] ); + t( "Nth Child With Whitespace", "p:nth-child( 1 )", ["firstp","sndp"] ); t( "Not Nth Child", "p:not(:nth-child(1))", ["ap","en","sap","first"] ); // Verify that the child position isn't being cached improperly @@ -322,15 +359,19 @@ test("pseudo - child", function() { t( "Nth-child", "#main form#form > *:nth-child(2)", ["text1"] ); t( "Nth-child", "#main form#form > :nth-child(2)", ["text1"] ); + t( "Nth-child", "#form select:first option:nth-child(-1)", [] ); t( "Nth-child", "#form select:first option:nth-child(3)", ["option1c"] ); t( "Nth-child", "#form select:first option:nth-child(0n+3)", ["option1c"] ); t( "Nth-child", "#form select:first option:nth-child(1n+0)", ["option1a", "option1b", "option1c", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(1n)", ["option1a", "option1b", "option1c", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(n)", ["option1a", "option1b", "option1c", "option1d"] ); + t( "Nth-child", "#form select:first option:nth-child(+n)", ["option1a", "option1b", "option1c", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(even)", ["option1b", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(odd)", ["option1a", "option1c"] ); t( "Nth-child", "#form select:first option:nth-child(2n)", ["option1b", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(2n+1)", ["option1a", "option1c"] ); + t( "Nth-child", "#form select:first option:nth-child(2n + 1)", ["option1a", "option1c"] ); + t( "Nth-child", "#form select:first option:nth-child(+2n + 1)", ["option1a", "option1c"] ); t( "Nth-child", "#form select:first option:nth-child(3n)", ["option1c"] ); t( "Nth-child", "#form select:first option:nth-child(3n+1)", ["option1a", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(3n+2)", ["option1b"] ); @@ -339,7 +380,9 @@ test("pseudo - child", function() { t( "Nth-child", "#form select:first option:nth-child(3n-2)", ["option1a", "option1d"] ); t( "Nth-child", "#form select:first option:nth-child(3n-3)", ["option1c"] ); t( "Nth-child", "#form select:first option:nth-child(3n+0)", ["option1c"] ); + t( "Nth-child", "#form select:first option:nth-child(-1n+3)", ["option1a", "option1b", "option1c"] ); t( "Nth-child", "#form select:first option:nth-child(-n+3)", ["option1a", "option1b", "option1c"] ); + t( "Nth-child", "#form select:first option:nth-child(-1n + 3)", ["option1a", "option1b", "option1c"] ); }); test("pseudo - misc", function() { -- cgit v1.2.3 From 3ab7b72919c8bd697e0cefab3b658bee0f51aef8 Mon Sep 17 00:00:00 2001 From: jeresig Date: Mon, 17 Jan 2011 18:07:52 -0500 Subject: Opera was falling back to undefined, which it didn't appreciate. --- test/unit/selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/selector.js b/test/unit/selector.js index f716cf4a9..27ad6e535 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -1,4 +1,4 @@ -module("selector", { teardown: moduleTeardown }); +module("selector"); test("element", function() { expect(21); -- cgit v1.2.3 From e01e8d434d6e8421b93f328549b753191bb14c33 Mon Sep 17 00:00:00 2001 From: jeresig Date: Mon, 17 Jan 2011 18:08:51 -0500 Subject: Accidentally removed the module teardown code. --- test/unit/selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/selector.js b/test/unit/selector.js index 27ad6e535..f716cf4a9 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -1,4 +1,4 @@ -module("selector"); +module("selector", { teardown: moduleTeardown }); test("element", function() { expect(21); -- cgit v1.2.3 From 9c76ac4f6d295a5b593048e54b4c4231c092e58a Mon Sep 17 00:00:00 2001 From: jeresig Date: Mon, 17 Jan 2011 18:12:42 -0500 Subject: Disabled a broken test for now, until WebKit browsers become more current. --- test/unit/selector.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/unit/selector.js b/test/unit/selector.js index f716cf4a9..6a3832555 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -58,7 +58,7 @@ if ( location.protocol != "file:" ) { } test("broken", function() { - expect(20); + expect(19); function broken(name, selector) { try { @@ -80,7 +80,10 @@ test("broken", function() { broken( "Doesn't exist", ":visble", [] ); broken( "Nth-child", ":nth-child", [] ); broken( "Nth-child", ":nth-child(-)", [] ); - broken( "Nth-child", ":nth-child(asdf)", [] ); + // Sigh. WebKit thinks this is a real selector in qSA + // They've already fixed this and it'll be coming into + // current browsers soon. + //broken( "Nth-child", ":nth-child(asdf)", [] ); broken( "Nth-child", ":nth-child(2n+-0)", [] ); broken( "Nth-child", ":nth-child(2+0)", [] ); broken( "Nth-child", ":nth-child(- 1n)", [] ); -- cgit v1.2.3 From 35aeac13369c66d21f67cd7524f1cf341e3550ce Mon Sep 17 00:00:00 2001 From: rwldrn Date: Mon, 17 Jan 2011 18:55:40 -0600 Subject: Update unit test for #7608 which was leaking timers. --- test/unit/css.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'test') diff --git a/test/unit/css.js b/test/unit/css.js index 1b2598741..555f13575 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -323,22 +323,13 @@ test(":visible selector works properly on children with a hidden parent (bug #45 test("internal ref to elem.runtimeStyle (bug #7608)", function () { expect(1); - - var result = true, - val = 10; + var result = true; - jQuery('
    ' + - '
     
    ').appendTo("#main"); - try { - // the bug is located within src/css.js - jQuery("#bug7608 #test").animate( { width: val }, 1000); - + jQuery("#foo").css( { width: "0%" } ).css("width"); } catch (e) { result = false; } ok( result, "elem.runtimeStyle does not throw exception" ); - - jQuery("#bug7608").remove(); }); -- cgit v1.2.3