Commit 7f94a5cc authored by Rick Waldron's avatar Rick Waldron

2.0: Rewrite data.js (Incl. event, manipulation, tests)

parent ed0e2d1e
var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, var data_user, data_priv,
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
rmultiDash = /([A-Z])/g; rmultiDash = /([A-Z])/g;
function internalData( elem, name, data, pvt ) { function Data() {
if ( !jQuery.acceptData( elem ) ) { // Nodes|Objects
return; this.owners = [];
} // Data objects
this.cache = [];
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 ) { Data.index = function( array, node ) {
// Only DOM nodes need a new unique ID for each element since their data return array.indexOf( node );
// 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 Data.prototype = {
// is serialized using JSON.stringify add: function( owner ) {
if ( !isNode ) { this.owners.push( owner );
cache[ id ].toJSON = jQuery.noop; return (this.cache[ this.owners.length - 1 ] = {});
},
set: function( owner, data, value ) {
var prop,
index = Data.index( this.owners, owner );
// 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;
// An object can be passed to jQuery.data instead of a key/value pair; this gets // Handle: [ owner, { properties } ] args
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else { } else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name ); // 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 ] ) ) {
thisCache = cache[ id ]; this.cache[ index ] = data;
// jQuery data() is stored in a separate object inside the object's internal data // Otherwise, copy the properties one-by-one to the cache object
// cache in order to avoid key collisions between internal data and user-defined } else {
// data. for ( prop in data ) {
if ( !pvt ) { this.cache[ index ][ prop ] = data[ prop ];
if ( !thisCache.data ) { }
thisCache.data = {}; }
} }
return this;
},
get: function( owner, key ) {
var cache,
index = Data.index( this.owners, owner );
// 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 ];
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 {
thisCache = thisCache.data; // If only an owner was specified, return the entire
} // cache object.
if ( key === undefined ) {
if ( data !== undefined ) { return this.get( owner );
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 // Allow setting or extending (existing objects) with an
ret = thisCache[ jQuery.camelCase( name ) ]; // object of properties, or a key and val
this.set( owner, key, value );
return value !== undefined ? value : key;
} }
} else { // Otherwise, this is a read request.
ret = thisCache; return this.get( owner, key );
} },
remove: function( owner, key ) {
return ret; var i, l, name,
} camel = jQuery.camelCase,
index = Data.index( this.owners, owner ),
function internalRemoveData( elem, name, pvt ) { cache = this.cache[ index ];
if ( !jQuery.acceptData( elem ) ) {
return;
}
var thisCache, i, l,
isNode = elem.nodeType,
// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
// If there is already no cache entry for this object, there is no
// purpose in continuing
if ( !cache[ id ] ) {
return;
}
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 if ( key === undefined ) {
name = jQuery.camelCase( name ); cache = {};
if ( name in thisCache ) { } else {
name = [ name ]; 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 { } else {
name = name.split(" "); // 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 ) );
} }
} else { i = 0;
// If "name" is an array of keys... l = name.length;
// 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 for ( ; i < l; i++ ) {
// and let the cache object itself get destroyed delete cache[ name[i] ];
if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { }
return;
} }
} }
} this.cache[ index ] = cache;
},
hasData: function( owner ) {
var index = Data.index( this.owners, owner );
// See jQuery.data for more information if ( index > -1 ) {
if ( !pvt ) { return !jQuery.isEmptyObject( this.cache[ index ] );
delete cache[ id ].data; }
return false;
},
discard: function( owner ) {
var index = Data.index( this.owners, owner );
// Don't destroy the parent cache unless the internal data object if ( index >= 0 ) {
// had been the only thing left in it this.owners.splice( index, 1 );
if ( !isEmptyDataObject( cache[ id ] ) ) { this.cache.splice( index, 1 );
return;
} }
return this;
} }
};
// 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 );
}
// Destroy the cache // These may be used throughout the jQuery core codebase
if ( isNode ) { data_user = new Data();
jQuery.cleanData( [ elem ], true ); data_priv = new Data();
// 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;
}
}
jQuery.extend({ 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 // Unique for each copy of jQuery on the page
// Non-digits removed to match rinlinejQuery // Non-digits removed to match rinlinejQuery
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), 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 ) { hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return data_user.hasData( elem ) || data_priv.hasData( elem );
return !!elem && !isEmptyDataObject( elem );
}, },
data: function( elem, name, data ) { data: function( elem, name, data ) {
return internalData( elem, name, data ); return data_user.access( elem, name, data );
}, },
removeData: function( elem, name ) { 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 ) { _data: function( elem, name, data ) {
return internalData( elem, name, data, true ); return data_priv.access( elem, name, data );
}, },
_removeData: function( elem, name ) { _removeData: function( elem, name ) {
return internalRemoveData( elem, name, true ); return data_priv.remove( elem, name );
},
// 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;
} }
}); });
...@@ -245,20 +209,19 @@ jQuery.fn.extend({ ...@@ -245,20 +209,19 @@ jQuery.fn.extend({
// Gets all values // Gets all values
if ( key === undefined ) { if ( key === undefined ) {
if ( this.length ) { 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; attrs = elem.attributes;
for ( ; i < attrs.length; i++ ) { for ( ; i < attrs.length; i++ ) {
name = attrs[i].name; name = attrs[i].name;
if ( !name.indexOf( "data-" ) ) { if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.slice(5) ); name = jQuery.camelCase( name.substring(5) );
dataAttr( elem, name, data[ name ] ); dataAttr( elem, name, data[ name ] );
} }
} }
jQuery._data( elem, "parsedAttrs", true ); data_priv.set( elem, "hasDataAttrs", true );
} }
} }
...@@ -268,53 +231,94 @@ jQuery.fn.extend({ ...@@ -268,53 +231,94 @@ jQuery.fn.extend({
// Sets multiple values // Sets multiple values
if ( typeof key === "object" ) { if ( typeof key === "object" ) {
return this.each(function() { return this.each(function() {
jQuery.data( this, key ); data_user.set( this, key );
}); });
} }
return jQuery.access( this, function( value ) { return jQuery.access( this, function( value ) {
var data,
camelKey = jQuery.camelCase( key );
// Get the Data...
if ( value === undefined ) { 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() { 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 ); }, null, value, arguments.length > 1, null, true );
}, },
removeData: function( key ) { removeData: function( key ) {
return this.each(function() { return this.each(function() {
jQuery.removeData( this, key ); data_user.remove( this, key );
}); });
} }
}); });
function dataAttr( elem, key, data ) { function dataAttr( elem, key, data ) {
var name;
// If nothing was found internally, try to fetch any // If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute // data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) { 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 ); data = elem.getAttribute( name );
if ( typeof data === "string" ) { if ( typeof data === "string" ) {
try { try {
data = data === "true" ? true : data = data === "true" ? true :
data === "false" ? false : data === "false" ? false :
data === "null" ? null : data === "null" ? null :
// Only convert to a number if it doesn't change the string // Only convert to a number if it doesn't change the string
+data + "" === data ? +data : +data + "" === data ? +data :
rbrace.test( data ) ? jQuery.parseJSON( data ) : rbrace.test( data ) ?
data; JSON.parse( data ) : data;
} catch( e ) {} } catch( e ) {}
// Make sure we set the data so it isn't changed later // Make sure we set the data so it isn't changed later
jQuery.data( elem, key, data ); data_user.set( elem, key, data );
} else { } else {
data = undefined; data = undefined;
} }
...@@ -322,20 +326,3 @@ function dataAttr( elem, key, data ) { ...@@ -322,20 +326,3 @@ function dataAttr( elem, key, data ) {
return 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;
}
...@@ -24,7 +24,7 @@ jQuery.event = { ...@@ -24,7 +24,7 @@ jQuery.event = {
var handleObjIn, eventHandle, tmp, var handleObjIn, eventHandle, tmp,
events, t, handleObj, events, t, handleObj,
special, handlers, type, namespaces, origType, 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) // Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) { if ( !elemData ) {
...@@ -130,7 +130,7 @@ jQuery.event = { ...@@ -130,7 +130,7 @@ jQuery.event = {
var j, origCount, tmp, var j, origCount, tmp,
events, t, handleObj, events, t, handleObj,
special, handlers, type, namespaces, origType, 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) ) { if ( !elemData || !(events = elemData.events) ) {
return; return;
......
...@@ -328,7 +328,7 @@ jQuery.fn.extend({ ...@@ -328,7 +328,7 @@ jQuery.fn.extend({
for ( i = 0; i < hasScripts; i++ ) { for ( i = 0; i < hasScripts; i++ ) {
node = scripts[ i ]; node = scripts[ i ];
if ( rscriptType.test( node.type || "" ) && 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 ) { if ( node.src ) {
// Hope ajax is available... // Hope ajax is available...
...@@ -511,11 +511,9 @@ jQuery.extend({ ...@@ -511,11 +511,9 @@ jQuery.extend({
}, },
cleanData: function( elems, /* internal */ acceptData ) { cleanData: function( elems, /* internal */ acceptData ) {
var id, data, elem, type, var data, elem, type,
l = elems.length, l = elems.length,
i = 0, i = 0,
internalKey = jQuery.expando,
cache = jQuery.cache,
special = jQuery.event.special; special = jQuery.event.special;
for ( ; i < l; i++ ) { for ( ; i < l; i++ ) {
...@@ -523,8 +521,7 @@ jQuery.extend({ ...@@ -523,8 +521,7 @@ jQuery.extend({
if ( acceptData || jQuery.acceptData( elem ) ) { if ( acceptData || jQuery.acceptData( elem ) ) {
id = elem[ internalKey ]; data = data_priv.access( elem );
data = id && cache[ id ];
if ( data ) { if ( data ) {
for ( type in data.events ) { for ( type in data.events ) {
...@@ -536,14 +533,10 @@ jQuery.extend({ ...@@ -536,14 +533,10 @@ jQuery.extend({
jQuery.removeEvent( elem, type, data.handle ); 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 ) { ...@@ -576,38 +569,49 @@ function setGlobalEval( elems, refElements ) {
i = 0; i = 0;
for ( ; i < l; i++ ) { 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 ) { 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; return;
} }
var i, l, type, // 1. Copy private data: events, handlers, etc.
oldData = jQuery._data( src ), if ( data_priv.hasData( src ) ) {
curData = jQuery._data( dest, oldData ), pdataOld = data_priv.access( src );
events = oldData.events; pdataCur = jQuery.extend( {}, pdataOld );
events = pdataOld.events;
data_priv.set( dest, pdataCur );
if ( events ) { if ( events ) {
delete curData.handle; delete pdataCur.handle;
curData.events = {}; pdataCur.events = {};
for ( type in events ) { for ( type in events ) {
for ( i = 0, l = events[ type ].length; i < l; i++ ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) {
jQuery.event.add( dest, type, events[ type ][ i ] ); jQuery.event.add( dest, type, events[ type ][ i ] );
}
} }
} }
} }
// make the cloned public data object a copy from the original // 2. Copy user data
if ( curData.data ) { if ( data_user.hasData( src ) ) {
curData.data = jQuery.extend( {}, curData.data ); udataOld = data_user.access( src );
udataCur = jQuery.extend( {}, udataOld );
data_user.set( dest, udataCur );
} }
} }
function getAll( context, tag ) { function getAll( context, tag ) {
var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
......
<!doctype html>
<html>
<script src="/dist/jquery.js"></script>
<script>
function report( event ) {
var payload = {
event: event.type
};
return top.postMessage( JSON.stringify(payload), "*" );
}
jQuery( window ).on( "beforeunload", function( event ) {
report( event );
}).on( "load", function( event ) {
setTimeout(function() {
window.location.reload();
}, 50);
});
</script>
</html>
...@@ -200,47 +200,53 @@ var Globals = (function() { ...@@ -200,47 +200,53 @@ var Globals = (function() {
*/ */
QUnit.expectJqData = function( elems, key ) { QUnit.expectJqData = function( elems, key ) {
var i, elem, expando; var i, elem, expando;
QUnit.current_testEnvironment.checkJqData = true;
if ( elems.jquery && elems.toArray ) { // As of jQuery 2.0, there will be no "cache"-data is
elems = elems.toArray(); // stored and managed completely below the API surface
} if ( jQuery.cache ) {
if ( !jQuery.isArray( elems ) ) { QUnit.current_testEnvironment.checkJqData = true;
elems = [ elems ];
}
for ( i = 0; i < elems.length; i++ ) { if ( elems.jquery && elems.toArray ) {
elem = elems[i]; elems = elems.toArray();
}
// jQuery.data only stores data for nodes in jQuery.cache, if ( !jQuery.isArray( elems ) ) {
// for other data targets the data is stored in the object itself, elems = [ elems ];
// 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 ]; for ( i = 0; i < elems.length; i++ ) {
elem = elems[i];
if ( expando === undefined ) {
// In this case the element exists fine, but // jQuery.data only stores data for nodes in jQuery.cache,
// jQuery.data (or internal data) was never (in)directly // for other data targets the data is stored in the object itself,
// called. // in that case we can't test that target for memory leaks.
// Since this method was called it means some data was // But we don't have to since in that case the data will/must will
// expected to be found, but since there is nothing, fail early // be available as long as the object is not garbage collected by
// (instead of in teardown). // the js engine, and when it is, the data will be removed with it.
notStrictEqual( expando, undefined, "Target for expectJqData must have an expando, for else there can be no data to expect." ); if ( !elem.nodeType ) {
} else { // Fixes false positives for dataTests(window), dataTests({}).
if ( expectedDataKeys[expando] ) { continue;
expectedDataKeys[expando].push( key ); }
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 { } else {
expectedDataKeys[expando] = [ key ]; if ( expectedDataKeys[expando] ) {
expectedDataKeys[expando].push( key );
} else {
expectedDataKeys[expando] = [ key ];
}
} }
} }
} }
}; };
QUnit.config.urlConfig.push( { QUnit.config.urlConfig.push( {
id: "jqdata", id: "jqdata",
...@@ -334,7 +340,7 @@ var Globals = (function() { ...@@ -334,7 +340,7 @@ var Globals = (function() {
} else { } else {
delete jQuery.ajaxSettings; delete jQuery.ajaxSettings;
} }
// Cleanup globals // Cleanup globals
Globals.cleanup(); Globals.cleanup();
......
...@@ -6,6 +6,33 @@ test("expando", function(){ ...@@ -6,6 +6,33 @@ test("expando", function(){
equal(jQuery.expando !== undefined, true, "jQuery is exposing the expando"); 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) { function dataTests (elem) {
var oldCacheLength, dataObj, internalDataObj, expected, actual; var oldCacheLength, dataObj, internalDataObj, expected, actual;
...@@ -106,6 +133,10 @@ test("Data is not being set on comment and text nodes", function() { ...@@ -106,6 +133,10 @@ test("Data is not being set on comment and text nodes", function() {
ok( !jQuery.hasData( jQuery("<span>text</span>").contents().data("foo", 0) ) ); ok( !jQuery.hasData( jQuery("<span>text</span>").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() { test("jQuery.acceptData", function() {
expect(9); expect(9);
...@@ -127,7 +158,7 @@ test("jQuery.acceptData", function() { ...@@ -127,7 +158,7 @@ test("jQuery.acceptData", function() {
ok( !jQuery.acceptData( document.createComment("") ), "comment" ); ok( !jQuery.acceptData( document.createComment("") ), "comment" );
ok( !jQuery.acceptData( document.createTextNode("") ), "text" ); ok( !jQuery.acceptData( document.createTextNode("") ), "text" );
}); });
*/
test(".data()", function() { test(".data()", function() {
expect(5); expect(5);
...@@ -424,7 +455,7 @@ if (window.JSON && window.JSON.stringify) { ...@@ -424,7 +455,7 @@ if (window.JSON && window.JSON.stringify) {
} }
test("jQuery.data should follow html5 specification regarding camel casing", function() { test("jQuery.data should follow html5 specification regarding camel casing", function() {
expect(10); expect(12);
var div = jQuery("<div id='myObject' data-w-t-f='ftw' data-big-a-little-a='bouncing-b' data-foo='a' data-foo-bar='b' data-foo-bar-baz='c'></div>") var div = jQuery("<div id='myObject' data-w-t-f='ftw' data-big-a-little-a='bouncing-b' data-foo='a' data-foo-bar='b' data-foo-bar-baz='c'></div>")
.prependTo("body"); .prependTo("body");
...@@ -445,6 +476,9 @@ test("jQuery.data should follow html5 specification regarding camel casing", fun ...@@ -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("fooBar"), "d", "Verify updated data-* key" );
equal( div.data("foo-bar"), "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(); div.remove();
}); });
......
...@@ -1371,75 +1371,20 @@ test("Submit event can be stopped (#11049)", function() { ...@@ -1371,75 +1371,20 @@ test("Submit event can be stopped (#11049)", function() {
// Test beforeunload event only if it supported (i.e. not Opera) // Test beforeunload event only if it supported (i.e. not Opera)
if ( window.onbeforeunload === null ) { if ( window.onbeforeunload === null ) {
asyncTest("on(beforeunload)", 4, function() { asyncTest("on(beforeunload)", 1, function() {
var win, var iframe = jQuery(jQuery.parseHTML("<iframe src='data/onbeforeunload.html'><iframe>"));
forIE6 = 0,
fired = false,
iframe = jQuery("<iframe src='data/iframe.html' />");
iframe.appendTo("#qunit-fixture").one( "load", function() {
win = this.contentWindow || this.contentDocument;
jQuery( win ).on( "beforeunload", function() {
fired = true;
ok( true, "beforeunload event is fired" );
});
strictEqual( win.onbeforeunload, null, "onbeforeunload property on window object still equals null" );
// In old Safari beforeunload event will not fire on iframes
jQuery( win ).on( "unload", function() {
if ( !fired ) {
ok( true, "This is suppose to be true only in old Safari" );
checker();
}
});
jQuery( win ).on( "beforeunload", function() {
// On iframe in IE6 beforeunload event will not fire if event is binded through window object,
// nevertheless, test should continue
window.setTimeout(function() {
if ( !forIE6 ) {
checker();
}
});
});
win.onbeforeunload = function() {
if ( !forIE6 ) {
forIE6++;
checker();
}
};
function checker() { window.onmessage = function( event ) {
ok( true, "window.onbeforeunload handler is called" ); var payload = JSON.parse( event.data );
iframe = jQuery("<iframe src='data/iframe.html' />");
iframe.appendTo("#qunit-fixture").one( "load", function() { ok( payload.event, "beforeunload", "beforeunload event" );
win = iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument;
jQuery( win ).on( "beforeunload", function() { iframe.remove();
strictEqual( win.onbeforeunload, null, "Event handler is fired, even when onbeforeunload property on window is nulled" ); window.onmessage = null;
start();
start(); };
});
jQuery( win ).on( "unload", function() {
if ( !fired ) {
jQuery( win ).trigger("beforeunload");
}
});
win.onbeforeunload = null;
win.location.reload();
});
}
win.location.reload(); iframe.appendTo("#qunit-fixture");
});
}); });
} }
...@@ -2567,7 +2512,7 @@ test( "Namespace preserved when passed an Event (#12739)", function() { ...@@ -2567,7 +2512,7 @@ test( "Namespace preserved when passed an Event (#12739)", function() {
e.handled = true; e.handled = true;
equal( e.namespace, "bar", "namespace is bar" ); equal( e.namespace, "bar", "namespace is bar" );
jQuery( e.target ).find("div").each(function() { jQuery( e.target ).find("div").each(function() {
jQuery( this ).triggerHandler( e ); jQuery( this ).triggerHandler( e );
}); });
} }
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment