diff --git a/src/data.js b/src/data.js index 596341c6a..448decd00 100644 --- a/src/data.js +++ b/src/data.js @@ -1,237 +1,201 @@ -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, +var data_user, data_priv, + rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; -function internalData( elem, name, data, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // 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, - - // 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[ internalKey ] : elem[ internalKey ] && internalKey; - - // 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 || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - 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[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // 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" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; +function Data() { + // Nodes|Objects + this.owners = []; + // Data objects + this.cache = []; } -function internalRemoveData( elem, name, pvt ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } +Data.index = function( array, node ) { + return array.indexOf( node ); +}; - var thisCache, i, l, - isNode = elem.nodeType, +Data.prototype = { + add: function( owner ) { + this.owners.push( owner ); + return (this.cache[ this.owners.length - 1 ] = {}); + }, + set: function( owner, data, value ) { + var prop, + index = Data.index( this.owners, owner ); - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + // If there is no entry for this "owner", create one inline + // and set the index as though an owner entry had always existed + if ( index === -1 ) { + this.add( owner ); + index = this.owners.length - 1; + } + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + this.cache[ index ][ data ] = value; - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } + // Handle: [ owner, { properties } ] args + } else { + // In the case where there was actually no "owner" entry and + // this.add( owner ) was called to create one, there will be + // a corresponding empty plain object in the cache. + if ( jQuery.isEmptyObject( this.cache[ index ] ) ) { + this.cache[ index ] = data; - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } + // Otherwise, copy the properties one-by-one to the cache object } else { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = name.concat( jQuery.map( name, jQuery.camelCase ) ); - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; + for ( prop in data ) { + this.cache[ index ][ prop ] = data[ prop ]; + } } } - } + return this; + }, + get: function( owner, key ) { + var cache, + index = Data.index( this.owners, owner ); - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; + // A valid cache is found, or needs to be created. + // New entries will be added and return the current + // empty data object to be used as a return reference + // return this.add( owner ); + // This logic was required by expectations made of the + // old data system. + cache = index === -1 ? + this.add( owner ) : this.cache[ index ]; - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + if ( value === undefined && (key && typeof key !== "object") ) { + // Assume this is a request to read the cached data + return this.get( owner, key ); + } else { + + // If only an owner was specified, return the entire + // cache object. + if ( key === undefined ) { + return this.get( owner ); + } + + // Allow setting or extending (existing objects) with an + // object of properties, or a key and val + this.set( owner, key, value ); + return value !== undefined ? value : key; } + // Otherwise, this is a read request. + return this.get( owner, key ); + }, + remove: function( owner, key ) { + var i, l, name, + camel = jQuery.camelCase, + index = Data.index( this.owners, owner ), + cache = this.cache[ index ]; + + if ( key === undefined ) { + cache = {}; + } else { + if ( cache ) { + // Support array or space separated string of keys + if ( !Array.isArray( key ) ) { + // Try the string as a key before any manipulation + // + + if ( key in cache ) { + name = [ key ]; + } else { + // Split the camel cased version by spaces unless a key with the spaces exists + name = camel( key ); + name = name in cache ? + [ name ] : name.split(" "); + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( camel ) ); + } + i = 0; + l = name.length; + + for ( ; i < l; i++ ) { + delete cache[ name[i] ]; + } + } + } + this.cache[ index ] = cache; + }, + hasData: function( owner ) { + var index = Data.index( this.owners, owner ); + + if ( index > -1 ) { + return !jQuery.isEmptyObject( this.cache[ index ] ); + } + return false; + }, + discard: function( owner ) { + var index = Data.index( this.owners, owner ); + + if ( index >= 0 ) { + this.owners.splice( index, 1 ); + this.cache.splice( index, 1 ); + } + return this; } +}; - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } +// This will be used by remove()/cleanData() in manipulation to sever +// remaining references to node objects. One day we'll replace the dual +// arrays with a WeakMap and this won't be an issue. +// (Splices the data objects out of the internal cache arrays) +function data_discard( owner ) { + data_user.discard( owner ); + data_priv.discard( owner ); } +// These may be used throughout the jQuery core codebase +data_user = new Data(); +data_priv = new Data(); + + jQuery.extend({ - cache: {}, - + // This is no longer relevant to jQuery core, but must remain + // supported for the sake of jQuery 1.9.x API surface compatibility. + acceptData: function() { + return true; + }, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); + return data_user.hasData( elem ) || data_priv.hasData( elem ); }, data: function( elem, name, data ) { - return internalData( elem, name, data ); + return data_user.access( elem, name, data ); }, removeData: function( elem, name ) { - return internalRemoveData( elem, name ); + return data_user.remove( elem, name ); }, - // For internal use only. + // TODO: Replace all calls to _data and _removeData with direct + // calls to + // + // data_priv.access( elem, name, data ); + // + // data_priv.remove( elem, name ); + // _data: function( elem, name, data ) { - return internalData( elem, name, data, true ); + return data_priv.access( elem, name, data ); }, _removeData: function( elem, name ) { - return internalRemoveData( elem, name, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - // Do not set data on non-element because it will not be cleared (#8335). - if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { - return false; - } - - var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; - - // nodes accept data unless otherwise specified; rejection can be conditional - return !noData || noData !== true && elem.getAttribute("classid") === noData; + return data_priv.remove( elem, name ); } }); @@ -245,20 +209,19 @@ jQuery.fn.extend({ // Gets all values if ( key === undefined ) { if ( this.length ) { - data = jQuery.data( elem ); + data = data_user.get( elem ); - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { attrs = elem.attributes; for ( ; i < attrs.length; i++ ) { name = attrs[i].name; - if ( !name.indexOf( "data-" ) ) { - name = jQuery.camelCase( name.slice(5) ); - + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); dataAttr( elem, name, data[ name ] ); } } - jQuery._data( elem, "parsedAttrs", true ); + data_priv.set( elem, "hasDataAttrs", true ); } } @@ -268,53 +231,94 @@ jQuery.fn.extend({ // Sets multiple values if ( typeof key === "object" ) { return this.each(function() { - jQuery.data( this, key ); + data_user.set( this, key ); }); } return jQuery.access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + // Get the Data... if ( value === undefined ) { - // Try to fetch any internally stored data first - return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key, undefined ); + if ( data !== undefined ) { + return data; + } + + // As a last resort, attempt to find + // the data by checking AGAIN, but with + // a camelCased key. + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return undefined; } + // Set the data... this.each(function() { - jQuery.data( this, key, value ); + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might ACTUALLY + // have dashes, we need to also store a copy of that + // unchanged property. + if ( /-/.test( key ) && data !== undefined ) { + data_user.set( this, key, value ); + } }); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each(function() { - jQuery.removeData( this, key ); + data_user.remove( this, key ); }); } }); function dataAttr( elem, key, data ) { + var name; + // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? + JSON.parse( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - + data_user.set( elem, key, data ); } else { data = undefined; } @@ -322,20 +326,3 @@ function dataAttr( elem, key, data ) { return data; } - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} diff --git a/src/event.js b/src/event.js index 4d7d6a895..4266a1ddb 100644 --- a/src/event.js +++ b/src/event.js @@ -24,7 +24,7 @@ jQuery.event = { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, - elemData = jQuery._data( elem ); + elemData = data_priv.get( elem ); // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { @@ -130,7 +130,7 @@ jQuery.event = { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); if ( !elemData || !(events = elemData.events) ) { return; diff --git a/src/manipulation.js b/src/manipulation.js index 708d7851d..d50ebddf7 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -328,7 +328,7 @@ jQuery.fn.extend({ for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && - !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src ) { // Hope ajax is available... @@ -511,11 +511,9 @@ jQuery.extend({ }, cleanData: function( elems, /* internal */ acceptData ) { - var id, data, elem, type, + var data, elem, type, l = elems.length, i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, special = jQuery.event.special; for ( ; i < l; i++ ) { @@ -523,8 +521,7 @@ jQuery.extend({ if ( acceptData || jQuery.acceptData( elem ) ) { - id = elem[ internalKey ]; - data = id && cache[ id ]; + data = data_priv.access( elem ); if ( data ) { for ( type in data.events ) { @@ -536,14 +533,10 @@ jQuery.extend({ jQuery.removeEvent( elem, type, data.handle ); } } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - delete cache[ id ]; - delete elem[ internalKey ]; - } } } + // Discard any remaining `private` and `user` data + data_discard( elem ); } } }); @@ -576,38 +569,49 @@ function setGlobalEval( elems, refElements ) { i = 0; for ( ; i < l; i++ ) { - jQuery._data( elems[ i ], "globalEval", !refElements || jQuery._data( refElements[ i ], "globalEval" ) ); + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); } } function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + if ( dest.nodeType !== 1 ) { return; } - var i, l, type, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = jQuery.extend( {}, pdataOld ); + events = pdataOld.events; - if ( events ) { - delete curData.handle; - curData.events = {}; + data_priv.set( dest, pdataCur ); - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } } } } - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); } } + function getAll( context, tag ) { var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : diff --git a/test/data/onbeforeunload.html b/test/data/onbeforeunload.html new file mode 100644 index 000000000..62c424e89 --- /dev/null +++ b/test/data/onbeforeunload.html @@ -0,0 +1,20 @@ + + + + + diff --git a/test/data/testrunner.js b/test/data/testrunner.js index f7aca724a..aa7c13019 100644 --- a/test/data/testrunner.js +++ b/test/data/testrunner.js @@ -200,47 +200,53 @@ var Globals = (function() { */ QUnit.expectJqData = function( elems, key ) { var i, elem, expando; - QUnit.current_testEnvironment.checkJqData = true; - if ( elems.jquery && elems.toArray ) { - elems = elems.toArray(); - } - if ( !jQuery.isArray( elems ) ) { - elems = [ elems ]; - } + // As of jQuery 2.0, there will be no "cache"-data is + // stored and managed completely below the API surface + if ( jQuery.cache ) { + QUnit.current_testEnvironment.checkJqData = true; - for ( i = 0; i < elems.length; i++ ) { - elem = elems[i]; - - // jQuery.data only stores data for nodes in jQuery.cache, - // for other data targets the data is stored in the object itself, - // in that case we can't test that target for memory leaks. - // But we don't have to since in that case the data will/must will - // be available as long as the object is not garbage collected by - // the js engine, and when it is, the data will be removed with it. - if ( !elem.nodeType ) { - // Fixes false positives for dataTests(window), dataTests({}). - continue; + if ( elems.jquery && elems.toArray ) { + elems = elems.toArray(); + } + if ( !jQuery.isArray( elems ) ) { + elems = [ elems ]; } - expando = elem[ jQuery.expando ]; + for ( i = 0; i < elems.length; i++ ) { + elem = elems[i]; - if ( expando === undefined ) { - // In this case the element exists fine, but - // jQuery.data (or internal data) was never (in)directly - // called. - // Since this method was called it means some data was - // expected to be found, but since there is nothing, fail early - // (instead of in teardown). - notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." ); - } else { - if ( expectedDataKeys[expando] ) { - expectedDataKeys[expando].push( key ); + // jQuery.data only stores data for nodes in jQuery.cache, + // for other data targets the data is stored in the object itself, + // in that case we can't test that target for memory leaks. + // But we don't have to since in that case the data will/must will + // be available as long as the object is not garbage collected by + // the js engine, and when it is, the data will be removed with it. + if ( !elem.nodeType ) { + // Fixes false positives for dataTests(window), dataTests({}). + continue; + } + + expando = elem[ jQuery.expando ]; + + if ( expando === undefined ) { + // In this case the element exists fine, but + // jQuery.data (or internal data) was never (in)directly + // called. + // Since this method was called it means some data was + // expected to be found, but since there is nothing, fail early + // (instead of in teardown). + notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." ); } else { - expectedDataKeys[expando] = [ key ]; + if ( expectedDataKeys[expando] ) { + expectedDataKeys[expando].push( key ); + } else { + expectedDataKeys[expando] = [ key ]; + } } } } + }; QUnit.config.urlConfig.push( { id: "jqdata", @@ -334,7 +340,7 @@ var Globals = (function() { } else { delete jQuery.ajaxSettings; } - + // Cleanup globals Globals.cleanup(); diff --git a/test/unit/data.js b/test/unit/data.js index 34c98140b..840014fd8 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -6,6 +6,33 @@ test("expando", function(){ equal(jQuery.expando !== undefined, true, "jQuery is exposing the expando"); }); +test( "jQuery.data & removeData, expected returns", function() { + expect(2); + + equal( + jQuery.data( document.body, "hello", "world" ), "world", + "jQuery.data( elem, key, value ) returns value" + ); + equal( + jQuery.removeData( document.body, "hello" ), undefined, + "jQuery.removeData( elem, key, value ) returns undefined" + ); + +}); + +test( "jQuery._data & _removeData, expected returns", function() { + expect(2); + + equal( + jQuery._data( document.body, "hello", "world" ), "world", + "jQuery.data( elem, key, value ) returns value" + ); + equal( + jQuery._removeData( document.body, "hello" ), undefined, + "jQuery.removeData( elem, key, value ) returns undefined" + ); +}); + function dataTests (elem) { var oldCacheLength, dataObj, internalDataObj, expected, actual; @@ -106,6 +133,10 @@ test("Data is not being set on comment and text nodes", function() { ok( !jQuery.hasData( jQuery("text").contents().data("foo", 0) ) ); }); +/* +// Since the new data system does not rely on exandos, limiting the type of +// nodes that can have data is no longer necessary. jQuery.acceptData is now irrelevant +// and should be removed from the library. test("jQuery.acceptData", function() { expect(9); @@ -127,7 +158,7 @@ test("jQuery.acceptData", function() { ok( !jQuery.acceptData( document.createComment("") ), "comment" ); ok( !jQuery.acceptData( document.createTextNode("") ), "text" ); }); - +*/ test(".data()", function() { expect(5); @@ -424,7 +455,7 @@ if (window.JSON && window.JSON.stringify) { } test("jQuery.data should follow html5 specification regarding camel casing", function() { - expect(10); + expect(12); var div = jQuery("
") .prependTo("body"); @@ -445,6 +476,9 @@ test("jQuery.data should follow html5 specification regarding camel casing", fun equal( div.data("fooBar"), "d", "Verify updated data-* key" ); equal( div.data("foo-bar"), "d", "Verify updated data-* key" ); + equal( div.data("fooBar"), "d", "Verify updated data-* key (fooBar)" ); + equal( div.data("foo-bar"), "d", "Verify updated data-* key (foo-bar)" ); + div.remove(); }); diff --git a/test/unit/event.js b/test/unit/event.js index 295a4b8d4..c72d873f0 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1371,75 +1371,20 @@ test("Submit event can be stopped (#11049)", function() { // Test beforeunload event only if it supported (i.e. not Opera) if ( window.onbeforeunload === null ) { - asyncTest("on(beforeunload)", 4, function() { - var win, - forIE6 = 0, - fired = false, - iframe = jQuery("