]> source.dussan.org Git - jquery.git/commitdiff
$.Callbacks, $.Topic and notify/progress on $.Deferred.
authorjaubourg <j@ubourg.net>
Mon, 12 Sep 2011 15:37:41 +0000 (17:37 +0200)
committertimmywil <timmywillisn@gmail.com>
Mon, 19 Sep 2011 19:42:30 +0000 (15:42 -0400)
22 files changed:
.gitignore
Makefile
src/ajax.js
src/callbacks.js [new file with mode: 0644]
src/core.js
src/deferred.js
src/queue.js
src/topic.js [new file with mode: 0644]
test/data/offset/absolute.html
test/data/offset/body.html
test/data/offset/fixed.html
test/data/offset/relative.html
test/data/offset/scroll.html
test/data/offset/static.html
test/data/offset/table.html
test/data/support/bodyBackground.html
test/data/support/boxModelIE.html
test/data/support/hiddenIFrameFF.html
test/index.html
test/unit/callbacks.js [new file with mode: 0644]
test/unit/deferred.js
test/unit/topic.js [new file with mode: 0644]

index 6ee92f269c386d01ffc18d5218de7db80f3db747..b3c04725066a65df1b12b5004866d10711ba2a19 100644 (file)
@@ -1,5 +1,6 @@
 src/selector.js
 dist
+.project
 .settings
 *~
 *.diff
index 59f63bdd9b60760ca59fda2da136789741abd55b..2d4409f8ab47c91a30d5fc98253122b64e9fb860 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,9 @@ COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
 POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js
 
 BASE_FILES = ${SRC_DIR}/core.js\
+       ${SRC_DIR}/callbacks.js\
        ${SRC_DIR}/deferred.js\
+       ${SRC_DIR}/topic.js\
        ${SRC_DIR}/support.js\
        ${SRC_DIR}/data.js\
        ${SRC_DIR}/queue.js\
index 623e9fe71956c934404856db0fb759ccf19ee72e..47aaf192fabab5d9d1adea76645bec0b41496ce5 100644 (file)
@@ -43,7 +43,7 @@ var r20 = /%20/g,
 
        // Document location segments
        ajaxLocParts,
-       
+
        // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
        allTypes = ["*/"] + ["*"];
 
@@ -403,7 +403,7 @@ jQuery.extend({
                                                jQuery( callbackContext ) : jQuery.event,
                        // Deferreds
                        deferred = jQuery.Deferred(),
-                       completeDeferred = jQuery._Deferred(),
+                       completeDeferred = jQuery.Callbacks( "once memory" ),
                        // Status-dependent callbacks
                        statusCode = s.statusCode || {},
                        // ifModified key
@@ -582,7 +582,7 @@ jQuery.extend({
                        }
 
                        // Complete
-                       completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+                       completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
 
                        if ( fireGlobals ) {
                                globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
@@ -597,7 +597,7 @@ jQuery.extend({
                deferred.promise( jqXHR );
                jqXHR.success = jqXHR.done;
                jqXHR.error = jqXHR.fail;
-               jqXHR.complete = completeDeferred.done;
+               jqXHR.complete = completeDeferred.add;
 
                // Status-dependent callbacks
                jqXHR.statusCode = function( map ) {
diff --git a/src/callbacks.js b/src/callbacks.js
new file mode 100644 (file)
index 0000000..a1caf91
--- /dev/null
@@ -0,0 +1,257 @@
+(function( jQuery ) {
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+       var object = flagsCache[ flags ] = {},
+               i, length;
+       flags = flags.split( /\s+/ );
+       for ( i = 0, length = flags.length; i < length; i++ ) {
+               object[ flags[i] ] = true;
+       }
+       return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *     flags:  an optional list of space-separated flags that will change how
+ *                     the callback list behaves
+ *
+ *     filter: an optional function that will be applied to each added callbacks,
+ *                     what filter returns will then be added provided it is not falsy.
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ *     once:                   will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *     memory:                 will keep track of previous values and will call any callback added
+ *                                     after the list has been fired right away with the latest "memorized"
+ *                                     values (like a Deferred)
+ *
+ *     queue:                  only first callback in the list is called each time the list is fired
+ *                                     (cannot be used in conjunction with memory)
+ *
+ *     unique:                 will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *     relocate:               like "unique" but will relocate the callback at the end of the list
+ *
+ *     stopOnFalse:    interrupt callings when a callback returns false
+ *
+ *     addAfterFire:   if callbacks are added while firing, then they are not executed until after
+ *                                     the next call to fire/fireWith
+ *
+ */
+jQuery.Callbacks = function( flags, filter ) {
+
+       // flags are optional
+       if ( typeof flags !== "string" ) {
+               filter = flags;
+               flags = undefined;
+       }
+
+       // Convert flags from String-formatted to Object-formatted
+       // (we check in cache first)
+       flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+       var // Actual callback list
+               list = [],
+               // Stack of fire calls for repeatable lists
+               stack = [],
+               // Last fire value (for non-forgettable lists)
+               memory,
+               // Flag to know if list is currently firing
+               firing,
+               // First callback to fire (used internally by add and fireWith)
+               firingStart,
+               // End of the loop when firing
+               firingLength,
+               // Index of currently firing callback (modified by remove if needed)
+               firingIndex,
+               // Add one or several callbacks to the list
+               add = function( args ) {
+                       var i,
+                               length,
+                               elem,
+                               type,
+                               actual;
+                       for ( i = 0, length = args.length; i < length; i++ ) {
+                               elem = args[ i ];
+                               type = jQuery.type( elem );
+                               if ( type === "array" ) {
+                                       // Inspect recursively
+                                       add( elem );
+                               } else if ( type === "function" ) {
+                                       // If we have to relocate, we remove the callback
+                                       // if it already exists
+                                       if ( flags.relocate ) {
+                                               self.remove( elem );
+                                       // Skip if we're in unique mode and callback is already in
+                                       } else if ( flags.unique && self.has( elem ) ) {
+                                               continue;
+                                       }
+                                       // Get the filtered function if needs be
+                                       actual = filter ? filter( elem ) : elem;
+                                       if ( actual ) {
+                                               list.push( [ elem, actual ] );
+                                       }
+                               }
+                       }
+               },
+               // Fire callbacks
+               fire = function( context, args ) {
+                       args = args || [];
+                       memory = !flags.memory || [ context, args ];
+                       firing = true;
+                       firingIndex = firingStart || 0;
+                       firingStart = 0;
+                       firingLength = list.length;
+                       for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+                               if ( list[ firingIndex ][ 1 ].apply( context, args ) === false && flags.stopOnFalse ) {
+                                       memory = true; // Mark as halted
+                                       break;
+                               } else if ( flags.queue ) {
+                                       list.splice( firingIndex, 1 );
+                                       break;
+                               }
+                       }
+                       firing = false;
+                       if ( list ) {
+                               if ( !flags.once ) {
+                                       if ( stack && stack.length ) {
+                                               memory = stack.shift();
+                                               self.fireWith( memory[ 0 ], memory[ 1 ] );
+                                       }
+                               } else if ( memory === true ) {
+                                       self.disable();
+                               } else {
+                                       list = [];
+                               }
+                       }
+               },
+               // Actual Callbacks object
+               self = {
+                       // Add a callback or a collection of callbacks to the list
+                       add: function() {
+                               if ( list ) {
+                                       var length = list.length;
+                                       add( arguments );
+                                       // Do we need to add the callbacks to the
+                                       // current firing batch?
+                                       if ( firing ) {
+                                               if ( !flags.addAfterFire ) {
+                                                       firingLength = list.length;
+                                               }
+                                       // With memory, if we're not firing then
+                                       // we should call right away, unless previous
+                                       // firing was halted (stopOnFalse)
+                                       } else if ( memory && memory !== true ) {
+                                               firingStart = length;
+                                               fire( memory[ 0 ], memory[ 1 ] );
+                                       }
+                               }
+                               return this;
+                       },
+                       // Remove a callback from the list
+                       remove: function() {
+                               if ( list ) {
+                                       var args = arguments,
+                                               argIndex = 0,
+                                               argLength = args.length;
+                                       for ( ; argIndex < argLength ; argIndex++ ) {
+                                               for ( var i = 0; i < list.length; i++ ) {
+                                                       if ( args[ argIndex ] === list[ i ][ 0 ] ) {
+                                                               // Handle firingIndex and firingLength
+                                                               if ( firing ) {
+                                                                       if ( i <= firingLength ) {
+                                                                               firingLength--;
+                                                                               if ( i <= firingIndex ) {
+                                                                                       firingIndex--;
+                                                                               }
+                                                                       }
+                                                               }
+                                                               // Remove the element
+                                                               list.splice( i--, 1 );
+                                                               // If we have some unicity property then
+                                                               // we only need to do this once
+                                                               if ( flags.unique || flags.relocate ) {
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               return this;
+                       },
+                       // Control if a given callback is in the list
+                       has: function( fn ) {
+                               if ( list ) {
+                                       var i = 0,
+                                               length = list.length;
+                                       for ( ; i < length; i++ ) {
+                                               if ( fn === list[ i ][ 0 ] ) {
+                                                       return true;
+                                               }
+                                       }
+                               }
+                               return false;
+                       },
+                       // Remove all callbacks from the list
+                       empty: function() {
+                               list = [];
+                               return this;
+                       },
+                       // Have the list do nothing anymore
+                       disable: function() {
+                               list = stack = memory = undefined;
+                               return this;
+                       },
+                       // Is it disabled?
+                       disabled: function() {
+                               return !list;
+                       },
+                       // Lock the list in its current state
+                       lock: function() {
+                               stack = undefined;
+                               if ( !memory || memory === true ) {
+                                       self.disable();
+                               }
+                               return this;
+                       },
+                       // Is it locked?
+                       locked: function() {
+                               return !stack;
+                       },
+                       // Call all callbacks with the given context and arguments
+                       fireWith: function( context, args ) {
+                               if ( stack ) {
+                                       if ( firing ) {
+                                               if ( !flags.once ) {
+                                                       stack.push( [ context, args ] );
+                                               }
+                                       } else if ( !( flags.once && memory ) ) {
+                                               fire( context, args );
+                                       }
+                               }
+                               return this;
+                       },
+                       // Call all the callbacks with the given arguments
+                       fire: function() {
+                               self.fireWith( this, arguments );
+                               return this;
+                       },
+                       // To know if the callbacks have already been called at least once
+                       fired: function() {
+                               return !!memory;
+                       }
+               };
+
+       return self;
+};
+
+})( jQuery );
index 18cf1023554786e15837a27298e227bc2af3fd7d..8d9a94f636653756f417090c99bc30f27c1cde97 100644 (file)
@@ -258,7 +258,7 @@ jQuery.fn = jQuery.prototype = {
                jQuery.bindReady();
 
                // Add the callback
-               readyList.done( fn );
+               readyList.add( fn );
 
                return this;
        },
@@ -413,7 +413,7 @@ jQuery.extend({
                        }
 
                        // If there are functions bound, to execute
-                       readyList.resolveWith( document, [ jQuery ] );
+                       readyList.fireWith( document, [ jQuery ] );
 
                        // Trigger any bound ready events
                        if ( jQuery.fn.trigger ) {
@@ -427,7 +427,7 @@ jQuery.extend({
                        return;
                }
 
-               readyList = jQuery._Deferred();
+               readyList = jQuery.Callbacks( "once memory" );
 
                // Catch cases where $(document).ready() is called after the
                // browser event has already occurred.
index e543f151825933388d51826f1a1b0bcbfab35057..57014af3de54fa91edfd3dd4e27048743a39677f 100644 (file)
 (function( jQuery ) {
 
 var // Promise methods
-       promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
+       promiseMethods = "done removeDone fail removeFail progress removeProgress isResolved isRejected promise then always pipe".split( " " ),
        // Static reference to slice
        sliceDeferred = [].slice;
 
 jQuery.extend({
-       // Create a simple deferred (one callbacks list)
-       _Deferred: function() {
-               var // callbacks list
-                       callbacks = [],
-                       // stored [ context , args ]
-                       fired,
-                       // to avoid firing when already doing so
-                       firing,
-                       // flag to know if the deferred has been cancelled
-                       cancelled,
-                       // the deferred itself
-                       deferred  = {
 
-                               // done( f1, f2, ...)
-                               done: function() {
-                                       if ( !cancelled ) {
-                                               var args = arguments,
-                                                       i,
-                                                       length,
-                                                       elem,
-                                                       type,
-                                                       _fired;
-                                               if ( fired ) {
-                                                       _fired = fired;
-                                                       fired = 0;
-                                               }
-                                               for ( i = 0, length = args.length; i < length; i++ ) {
-                                                       elem = args[ i ];
-                                                       type = jQuery.type( elem );
-                                                       if ( type === "array" ) {
-                                                               deferred.done.apply( deferred, elem );
-                                                       } else if ( type === "function" ) {
-                                                               callbacks.push( elem );
-                                                       }
-                                               }
-                                               if ( _fired ) {
-                                                       deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
-                                               }
-                                       }
-                                       return this;
-                               },
+       Deferred: function( func ) {
+               var doneList = jQuery.Callbacks( "once memory" ),
+                       failList = jQuery.Callbacks( "once memory" ),
+                       progressList = jQuery.Callbacks( "memory" ),
+                       promise,
+                       deferred = {
+                               // Copy existing methods from lists
+                               done: doneList.add,
+                               removeDone: doneList.remove,
+                               fail: failList.add,
+                               removeFail: failList.remove,
+                               progress: progressList.add,
+                               removeProgress: progressList.remove,
+                               resolve: doneList.fire,
+                               resolveWith: doneList.fireWith,
+                               reject: failList.fire,
+                               rejectWith: failList.fireWith,
+                               notify: progressList.fire,
+                               notifyWith: progressList.fireWith,
+                               isResolved: doneList.fired,
+                               isRejected: failList.fired,
 
-                               // resolve with given context and args
-                               resolveWith: function( context, args ) {
-                                       if ( !cancelled && !fired && !firing ) {
-                                               // make sure args are available (#8421)
-                                               args = args || [];
-                                               firing = 1;
-                                               try {
-                                                       while( callbacks[ 0 ] ) {
-                                                               callbacks.shift().apply( context, args );
-                                                       }
-                                               }
-                                               finally {
-                                                       fired = [ context, args ];
-                                                       firing = 0;
-                                               }
-                                       }
+                               // Create Deferred-specific methods
+                               then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+                                       deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
                                        return this;
                                },
-
-                               // resolve with this as context and given arguments
-                               resolve: function() {
-                                       deferred.resolveWith( this, arguments );
-                                       return this;
+                               always: function() {
+                                       return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
                                },
-
-                               // Has this deferred been resolved?
-                               isResolved: function() {
-                                       return !!( firing || fired );
+                               pipe: function( fnDone, fnFail, fnProgress ) {
+                                       return jQuery.Deferred(function( newDefer ) {
+                                               jQuery.each( {
+                                                       done: [ fnDone, "resolve" ],
+                                                       fail: [ fnFail, "reject" ],
+                                                       progress: [ fnProgress, "notify" ]
+                                               }, function( handler, data ) {
+                                                       var fn = data[ 0 ],
+                                                               action = data[ 1 ],
+                                                               returned;
+                                                       if ( jQuery.isFunction( fn ) ) {
+                                                               deferred[ handler ](function() {
+                                                                       returned = fn.apply( this, arguments );
+                                                                       if ( returned && jQuery.isFunction( returned.promise ) ) {
+                                                                               returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+                                                                       } else {
+                                                                               newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+                                                                       }
+                                                               });
+                                                       } else {
+                                                               deferred[ handler ]( newDefer[ action ] );
+                                                       }
+                                               });
+                                       }).promise();
                                },
-
-                               // Cancel
-                               cancel: function() {
-                                       cancelled = 1;
-                                       callbacks = [];
-                                       return this;
+                               // Get a promise for this deferred
+                               // If obj is provided, the promise aspect is added to the object
+                               promise: function( obj ) {
+                                       if ( obj == null ) {
+                                               if ( promise ) {
+                                                       return promise;
+                                               }
+                                               promise = obj = {};
+                                       }
+                                       var i = promiseMethods.length;
+                                       while( i-- ) {
+                                               obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+                                       }
+                                       return obj;
                                }
                        };
 
-               return deferred;
-       },
+               // Handle lists exclusiveness
+               deferred.done( failList.disable, progressList.lock )
+                       .fail( doneList.disable, progressList.lock );
 
-       // Full fledged deferred (two callbacks list)
-       Deferred: function( func ) {
-               var deferred = jQuery._Deferred(),
-                       failDeferred = jQuery._Deferred(),
-                       promise;
-               // Add errorDeferred methods, then and promise
-               jQuery.extend( deferred, {
-                       then: function( doneCallbacks, failCallbacks ) {
-                               deferred.done( doneCallbacks ).fail( failCallbacks );
-                               return this;
-                       },
-                       always: function() {
-                               return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
-                       },
-                       fail: failDeferred.done,
-                       rejectWith: failDeferred.resolveWith,
-                       reject: failDeferred.resolve,
-                       isRejected: failDeferred.isResolved,
-                       pipe: function( fnDone, fnFail ) {
-                               return jQuery.Deferred(function( newDefer ) {
-                                       jQuery.each( {
-                                               done: [ fnDone, "resolve" ],
-                                               fail: [ fnFail, "reject" ]
-                                       }, function( handler, data ) {
-                                               var fn = data[ 0 ],
-                                                       action = data[ 1 ],
-                                                       returned;
-                                               if ( jQuery.isFunction( fn ) ) {
-                                                       deferred[ handler ](function() {
-                                                               returned = fn.apply( this, arguments );
-                                                               if ( returned && jQuery.isFunction( returned.promise ) ) {
-                                                                       returned.promise().then( newDefer.resolve, newDefer.reject );
-                                                               } else {
-                                                                       newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
-                                                               }
-                                                       });
-                                               } else {
-                                                       deferred[ handler ]( newDefer[ action ] );
-                                               }
-                                       });
-                               }).promise();
-                       },
-                       // Get a promise for this deferred
-                       // If obj is provided, the promise aspect is added to the object
-                       promise: function( obj ) {
-                               if ( obj == null ) {
-                                       if ( promise ) {
-                                               return promise;
-                                       }
-                                       promise = obj = {};
-                               }
-                               var i = promiseMethods.length;
-                               while( i-- ) {
-                                       obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
-                               }
-                               return obj;
-                       }
-               });
-               // Make sure only one callback list will be used
-               deferred.done( failDeferred.cancel ).fail( deferred.cancel );
-               // Unexpose cancel
-               delete deferred.cancel;
                // Call given func if any
                if ( func ) {
                        func.call( deferred, deferred );
                }
+
+               // All done!
                return deferred;
        },
 
        // Deferred helper
        when: function( firstParam ) {
-               var args = arguments,
+               var args = sliceDeferred.call( arguments, 0 ),
                        i = 0,
                        length = args.length,
+                       pValues = new Array( length ),
                        count = length,
+                       pCount = length,
                        deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
                                firstParam :
-                               jQuery.Deferred();
+                               jQuery.Deferred(),
+                       promise = deferred.promise();
                function resolveFunc( i ) {
                        return function( value ) {
                                args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
                                if ( !( --count ) ) {
-                                       // Strange bug in FF4:
-                                       // Values changed onto the arguments object sometimes end up as undefined values
-                                       // outside the $.when method. Cloning the object into a fresh array solves the issue
-                                       deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+                                       deferred.resolveWith( deferred, args );
                                }
                        };
                }
+               function progressFunc( i ) {
+                       return function( value ) {
+                               pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+                               deferred.notifyWith( promise, pValues );
+                       };
+               }
                if ( length > 1 ) {
                        for( ; i < length; i++ ) {
-                               if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
-                                       args[ i ].promise().then( resolveFunc(i), deferred.reject );
+                               if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+                                       args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
                                } else {
                                        --count;
                                }
@@ -192,8 +132,8 @@ jQuery.extend({
                } else if ( deferred !== firstParam ) {
                        deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
                }
-               return deferred.promise();
+               return promise;
        }
 });
 
-})( jQuery );
\ No newline at end of file
+})( jQuery );
index cd6aeb26b4d14f23123d8452fe2037e458ccdac2..0c678064eb1bf54cd71a48e2a44f62de73a2584f 100644 (file)
@@ -14,7 +14,7 @@ function handleQueueMarkDefer( elem, type, src ) {
                        if ( !jQuery._data( elem, queueDataKey ) &&
                                !jQuery._data( elem, markDataKey ) ) {
                                jQuery.removeData( elem, deferDataKey, true );
-                               defer.resolve();
+                               defer.fire();
                        }
                }, 0 );
        }
@@ -160,9 +160,9 @@ jQuery.fn.extend({
                        if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
                                        ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
                                                jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
-                                       jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
+                                       jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
                                count++;
-                               tmp.done( resolve );
+                               tmp.add( resolve );
                        }
                }
                resolve();
diff --git a/src/topic.js b/src/topic.js
new file mode 100644 (file)
index 0000000..c856db8
--- /dev/null
@@ -0,0 +1,45 @@
+(function( jQuery ) {
+
+       var topics = {},
+               sliceTopic = [].slice;
+
+       jQuery.Topic = function( id ) {
+               var callbacks,
+                       method,
+                       topic = id && topics[ id ];
+               if ( !topic ) {
+                       callbacks = jQuery.Callbacks();
+                       topic = {
+                               publish: callbacks.fire,
+                               subscribe: callbacks.add,
+                               unsubscribe: callbacks.remove
+                       };
+                       if ( id ) {
+                               topics[ id ] = topic;
+                       }
+               }
+               return topic;
+       };
+
+       jQuery.extend({
+               subscribe: function( id ) {
+                       var topic = jQuery.Topic( id ),
+                               args = sliceTopic.call( arguments, 1 );
+                       topic.subscribe.apply( topic, args );
+                       return {
+                               topic: topic,
+                               args: args
+                       };
+               },
+               unsubscribe: function( id ) {
+                       var topic = id && id.topic || jQuery.Topic( id );
+                       topic.unsubscribe.apply( topic, id && id.args ||
+                                       sliceTopic.call( arguments, 1 ) );
+               },
+               publish: function( id ) {
+                       var topic = jQuery.Topic( id );
+                       topic.publish.apply( topic, sliceTopic.call( arguments, 1 ) );
+               }
+       });
+
+})( jQuery );
index 9d7990a37c892caa3166756925c9c060b7a12ec5..f8bea437b50bf4fdbee425ea64fc5f38eb62113e 100644 (file)
@@ -16,7 +16,9 @@
                        #positionTest { position: absolute; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index 8dbf282df7d95663a4b9a5c97c68565ecabd3b26..9843937ab97c4df49a35382fa58428cdb113d325 100644 (file)
@@ -9,7 +9,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index 81ba4ca7dc30560bf5512eff0d12cf45bb68734f..43ef4f68e975963b0467691696e7faa8afeca7e2 100644 (file)
@@ -13,7 +13,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index 280a2fc0e319e9dc055182ababed4b9799f8155e..933db9b0f92c4c07d7c74a426842b31dd211e8be 100644 (file)
@@ -11,7 +11,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index a0d1f4d1827e5f5b1b44010641772528430990e6..b178884234f6c0dc9467dfe0d10a54f3816e846b 100644 (file)
@@ -14,7 +14,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index a61b6d10ee5595d9380c045fa69eccfdea87b398..07e75700429df5fad88cad9b74d3766d653c946f 100644 (file)
@@ -11,7 +11,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index 11fb0e795d62934a5520c872a10ad9e530008162..52e2200657c9ea402c7ec8ef8365cb7916e0616e 100644 (file)
@@ -11,7 +11,9 @@
                        #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; }
                </style>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/sizzle/sizzle.js"></script>
                <script src="../../../src/sizzle-jquery.js"></script>
index 4fd9df7f60b31a51b5110caf0034b0b1a98a12ae..340784bb0223907af98a2c367cb5811492761a54 100644 (file)
@@ -11,7 +11,9 @@
 <body>
        <div>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/data.js"></script>
                <script src="../../../src/queue.js"></script>
index e3c907d3474b6a54d6fc2d80672d40aa73f689eb..a46eee16e0c676cdbc00cfd2b778c8e098aeed18 100644 (file)
@@ -2,7 +2,9 @@
 <html>
 <body>
        <script src="../../../src/core.js"></script>
+       <script src="../../../src/callbacks.js"></script>
        <script src="../../../src/deferred.js"></script>
+       <script src="../../../src/topic.js"></script>
        <script src="../../../src/support.js"></script>
        <script src="../../../src/data.js"></script>
        <script src="../../../src/queue.js"></script>
index 000ac851a4be3f380c81ffbf915f95cea3b92133..eae43871197700aa2f77a1b36ea1bc7d65bd81ce 100644 (file)
@@ -1,7 +1,9 @@
 <html>
        <head>
                <script src="../../../src/core.js"></script>
+               <script src="../../../src/callbacks.js"></script>
                <script src="../../../src/deferred.js"></script>
+               <script src="../../../src/topic.js"></script>
                <script src="../../../src/support.js"></script>
                <script src="../../../src/data.js"></script>
                <script src="../../../src/queue.js"></script>
index 05a444e01d309ef925aa33393c50b838993fa0a2..10c2e5f6cc82867660e274631717bd1b314ae79f 100644 (file)
@@ -9,7 +9,9 @@
        <script src="data/testinit.js"></script>
 
        <script src="../src/core.js"></script>
+       <script src="../src/callbacks.js"></script>
        <script src="../src/deferred.js"></script>
+       <script src="../src/topic.js"></script>
        <script src="../src/support.js"></script>
        <script src="../src/data.js"></script>
        <script src="../src/queue.js"></script>
@@ -35,7 +37,9 @@
 
        <script src="unit/core.js"></script>
        <script src="unit/support.js"></script>
+       <script src="unit/callbacks.js"></script>
        <script src="unit/deferred.js"></script>
+       <script src="unit/topic.js"></script>
        <script src="unit/data.js"></script>
        <script src="unit/queue.js"></script>
        <script src="unit/attributes.js"></script>
diff --git a/test/unit/callbacks.js b/test/unit/callbacks.js
new file mode 100644 (file)
index 0000000..fcc3ee4
--- /dev/null
@@ -0,0 +1,194 @@
+module("callbacks", { teardown: moduleTeardown });
+
+(function() {
+
+var output,
+       addToOutput = function( string ) {
+               return function() {
+                       output += string;
+               };
+       },
+       outputA = addToOutput( "A" ),
+       outputB = addToOutput( "B" ),
+       outputC = addToOutput( "C" ),
+       tests = {
+               "":                                                     "XABC   X               XABCABCC        X       XBB X   XABA    X",
+               "once":                                         "XABC   X               X                       X       X       X       XABA    X",
+               "memory":                                       "XABC   XABC    XABCABCCC       XA      XBB     XB      XABA    XC",
+               "unique":                                       "XABC   X               XABCA           X       XBB     X       XAB             X",
+               "relocate":                                     "XABC   X               XAABC           X       XBB X   XBA             X",
+               "stopOnFalse":                          "XABC   X               XABCABCC        X       XBB     X       XA              X",
+               "addAfterFire":                         "XAB    X               XABCAB          X       XBB     X       XABA    X",
+               "queue":                                        "XA             X               XB                      X       XB      X       XA              X",
+               "once memory":                          "XABC   XABC    X                       XA      X       XA      XABA    XC",
+               "once unique":                          "XABC   X               X                       X       X       X       XAB             X",
+               "once relocate":                        "XABC   X               X                       X       X       X       XBA             X",
+               "once stopOnFalse":                     "XABC   X               X                       X       X       X       XA              X",
+               "once addAfterFire":            "XAB    X               X                       X       X       X       XABA    X",
+               "memory unique":                        "XABC   XA              XABCA           XA      XBB     XB      XAB             XC",
+               "memory relocate":                      "XABC   XB              XAABC           XA      XBB     XB      XBA             XC",
+               "memory stopOnFalse":           "XABC   XABC    XABCABCCC       XA      XBB     XB      XA              X",
+               "memory addAfterFire":          "XAB    XAB             XABCABC         XA      XBB     XB      XABA    XC",
+               "unique relocate":                      "XABC   X               XAABC           X       XBB     X       XBA             X",
+               "unique stopOnFalse":           "XABC   X               XABCA           X       XBB     X       XA              X",
+               "unique addAfterFire":          "XAB    X               XABCA           X       XBB     X       XAB             X",
+               "relocate stopOnFalse":         "XABC   X               XAABC           X       XBB     X       X               X",
+               "relocate addAfterFire":        "XAB    X               XAA                     X       XBB     X       XBA             X",
+               "stopOnFalse addAfterFire":     "XAB    X               XABCAB          X       XBB     X       XA              X"
+       },
+       filters = {
+               "no filter": undefined,
+               "filter": function( fn ) {
+                       return function() {
+                               return fn.apply( this, arguments );
+                       };
+               }
+       };
+
+jQuery.each( tests, function( flags, resultString ) {
+
+               jQuery.each( filters, function( filterLabel, filter ) {
+
+                       test( "jQuery.Callbacks( \"" + flags + "\" ) - " + filterLabel, function() {
+
+                               expect( 19 );
+
+                               // Give qunit a little breathing room
+                               stop();
+                               setTimeout( start, 0 );
+
+                               var cblist;
+                                       results = resultString.split( /\s+/ );
+
+                               // Basic binding and firing
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add(function( str ) {
+                                       output += str;
+                               });
+                               cblist.fire( "A" );
+                               strictEqual( output, "XA", "Basic binding and firing" );
+                               output = "X";
+                               cblist.disable();
+                               cblist.add(function( str ) {
+                                       output += str;
+                               });
+                               strictEqual( output, "X", "Adding a callback after disabling" );
+                               cblist.fire( "A" );
+                               strictEqual( output, "X", "Firing after disabling" );
+
+                               // Basic binding and firing (context, arguments)
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add(function() {
+                                       equals( this, window, "Basic binding and firing (context)" );
+                                       output += Array.prototype.join.call( arguments, "" );
+                               });
+                               cblist.fireWith( window, [ "A", "B" ] );
+                               strictEqual( output, "XAB", "Basic binding and firing (arguments)" );
+
+                               // fireWith with no arguments
+                               output = "";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add(function() {
+                                       equals( this, window, "fireWith with no arguments (context is window)" );
+                                       strictEqual( arguments.length, 0, "fireWith with no arguments (no arguments)" );
+                               });
+                               cblist.fireWith();
+
+                               // Basic binding, removing and firing
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( outputA, outputB, outputC );
+                               cblist.remove( outputB, outputC );
+                               cblist.fire();
+                               strictEqual( output, "XA", "Basic binding, removing and firing" );
+
+                               // Empty
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( outputA );
+                               cblist.add( outputB );
+                               cblist.add( outputC );
+                               cblist.empty();
+                               cblist.fire();
+                               strictEqual( output, "X", "Empty" );
+
+                               // Locking
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( function( str ) {
+                                       output += str;
+                               });
+                               cblist.lock();
+                               cblist.add( function( str ) {
+                                       output += str;
+                               });
+                               cblist.fire( "A" );
+                               cblist.add( function( str ) {
+                                       output += str;
+                               });
+                               strictEqual( output, "X", "Lock early" );
+
+                               // Ordering
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( function() {
+                                       cblist.add( outputC );
+                                       outputA();
+                               }, outputB );
+                               cblist.fire();
+                               strictEqual( output, results.shift(), "Proper ordering" );
+
+                               // Add and fire again
+                               output = "X";
+                               cblist.add( function() {
+                                       cblist.add( outputC );
+                                       outputA();
+                               }, outputB );
+                               strictEqual( output, results.shift(), "Add after fire" );
+
+                               output = "X";
+                               cblist.fire();
+                               strictEqual( output, results.shift(), "Fire again" );
+
+                               // Multiple fire
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( function( str ) {
+                                       output += str;
+                               } );
+                               cblist.fire( "A" );
+                               strictEqual( output, "XA", "Multiple fire (first fire)" );
+                               output = "X";
+                               cblist.add( function( str ) {
+                                       output += str;
+                               } );
+                               strictEqual( output, results.shift(), "Multiple fire (first new callback)" );
+                               output = "X";
+                               cblist.fire( "B" );
+                               strictEqual( output, results.shift(), "Multiple fire (second fire)" );
+                               output = "X";
+                               cblist.add( function( str ) {
+                                       output += str;
+                               } );
+                               strictEqual( output, results.shift(), "Multiple fire (second new callback)" );
+
+                               // Return false
+                               output = "X";
+                               cblist = jQuery.Callbacks( flags );
+                               cblist.add( outputA, function() { return false; }, outputB );
+                               cblist.add( outputA );
+                               cblist.fire();
+                               strictEqual( output, results.shift(), "Callback returning false" );
+
+                               // Add another callback (to control lists with memory do not fire anymore)
+                               output = "X";
+                               cblist.add( outputC );
+                               strictEqual( output, results.shift(), "Adding a callback after one returned false" );
+
+                       });
+               });
+});
+
+})();
index fbe2907078df69e59f54b5d895f54ca12c715815..de74aeccdf6f8c97950164fbd8553bd1a949df71 100644 (file)
@@ -1,111 +1,5 @@
 module("deferred", { teardown: moduleTeardown });
 
-jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
-
-       function createDeferred() {
-               return withNew ? new jQuery._Deferred() : jQuery._Deferred();
-       }
-
-       test("jQuery._Deferred" + withNew, function() {
-
-               expect( 11 );
-
-               var deferred,
-                       object,
-                       test;
-
-               deferred = createDeferred();
-
-               test = false;
-
-               deferred.done( function( value ) {
-                       equals( value , "value" , "Test pre-resolve callback" );
-                       test = true;
-               } );
-
-               deferred.resolve( "value" );
-
-               ok( test , "Test pre-resolve callbacks called right away" );
-
-               test = false;
-
-               deferred.done( function( value ) {
-                       equals( value , "value" , "Test post-resolve callback" );
-                       test = true;
-               } );
-
-               ok( test , "Test post-resolve callbacks called right away" );
-
-               deferred.cancel();
-
-               test = true;
-
-               deferred.done( function() {
-                       ok( false , "Cancel was ignored" );
-                       test = false;
-               } );
-
-               ok( test , "Test cancel" );
-
-               deferred = createDeferred().resolve();
-
-               try {
-                       deferred.done( function() {
-                               throw "Error";
-                       } , function() {
-                               ok( true , "Test deferred do not cancel on exception" );
-                       } );
-               } catch( e ) {
-                       strictEqual( e , "Error" , "Test deferred propagates exceptions");
-                       deferred.done();
-               }
-
-               test = "";
-               deferred = createDeferred().done( function() {
-
-                       test += "A";
-
-               }, function() {
-
-                       test += "B";
-
-               } ).resolve();
-
-               strictEqual( test , "AB" , "Test multiple done parameters" );
-
-               test = "";
-
-               deferred.done( function() {
-
-                       deferred.done( function() {
-
-                               test += "C";
-
-                       } );
-
-                       test += "A";
-
-               }, function() {
-
-                       test += "B";
-               } );
-
-               strictEqual( test , "ABC" , "Test done callbacks order" );
-
-               deferred = createDeferred();
-
-               deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) {
-                       ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" );
-               });
-
-               // #8421
-               deferred = createDeferred();
-               deferred.resolveWith().done(function() {
-                       ok( true, "Test resolveWith can be called with no argument" );
-               });
-       });
-} );
-
 jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
 
        function createDeferred( fn ) {
@@ -114,7 +8,7 @@ jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
 
        test("jQuery.Deferred" + withNew, function() {
 
-               expect( 8 );
+               expect( 14 );
 
                createDeferred().resolve().then( function() {
                        ok( true , "Success on resolve" );
@@ -140,6 +34,20 @@ jQuery.each( [ "", " - new operator" ], function( _, withNew ) {
                }).then( function( value ) {
                        strictEqual( value , "done" , "Passed function executed" );
                });
+
+               jQuery.each( "resolve reject".split( " " ), function( _, change ) {
+                       createDeferred( function( defer ) {
+                               var checked = 0;
+                               defer.progress(function( value ) {
+                                       strictEqual( value, checked, "Progress: right value (" + value + ") received" );
+                               });
+                               for( checked = 0; checked < 3 ; checked++ ) {
+                                       defer.notify( checked );
+                               }
+                               defer[ change ]();
+                               defer.notify();
+                       });
+               });
        });
 } );
 
@@ -215,6 +123,34 @@ test( "jQuery.Deferred.pipe - filtering (fail)", function() {
        });
 });
 
+test( "jQuery.Deferred.pipe - filtering (progress)", function() {
+
+       expect(3);
+
+       var defer = jQuery.Deferred(),
+               piped = defer.pipe( null, null, function( a, b ) {
+                       return a * b;
+               } ),
+               value1,
+               value2,
+               value3;
+
+       piped.progress(function( result ) {
+               value3 = result;
+       });
+
+       defer.progress(function( a, b ) {
+               value1 = a;
+               value2 = b;
+       });
+
+       defer.notify( 2, 3 );
+
+       strictEqual( value1, 2, "first progress value ok" );
+       strictEqual( value2, 3, "second progress value ok" );
+       strictEqual( value3, 6, "result of filter ok" );
+});
+
 test( "jQuery.Deferred.pipe - deferred (done)", function() {
 
        expect(3);
@@ -275,6 +211,36 @@ test( "jQuery.Deferred.pipe - deferred (fail)", function() {
        strictEqual( value3, 6, "result of filter ok" );
 });
 
+test( "jQuery.Deferred.pipe - deferred (progress)", function() {
+
+       expect(3);
+
+       var defer = jQuery.Deferred(),
+               piped = defer.pipe( null, null, function( a, b ) {
+                       return jQuery.Deferred(function( defer ) {
+                               defer.resolve( a * b );
+                       });
+               } ),
+               value1,
+               value2,
+               value3;
+
+       piped.done(function( result ) {
+               value3 = result;
+       });
+
+       defer.progress(function( a, b ) {
+               value1 = a;
+               value2 = b;
+       });
+
+       defer.notify( 2, 3 );
+
+       strictEqual( value1, 2, "first progress value ok" );
+       strictEqual( value2, 3, "second progress value ok" );
+       strictEqual( value3, 6, "result of filter ok" );
+});
+
 test( "jQuery.Deferred.pipe - context", function() {
 
        expect(4);
@@ -301,7 +267,6 @@ test( "jQuery.Deferred.pipe - context", function() {
        });
 });
 
-
 test( "jQuery.when" , function() {
 
        expect( 23 );
@@ -345,36 +310,54 @@ test( "jQuery.when" , function() {
 
 test("jQuery.when - joined", function() {
 
-       expect(25);
+       expect(53);
 
        var deferreds = {
                        value: 1,
                        success: jQuery.Deferred().resolve( 1 ),
                        error: jQuery.Deferred().reject( 0 ),
-                       futureSuccess: jQuery.Deferred(),
-                       futureError: jQuery.Deferred()
+                       futureSuccess: jQuery.Deferred().notify( true ),
+                       futureError: jQuery.Deferred().notify( true ),
+                       notify: jQuery.Deferred().notify( true )
                },
                willSucceed = {
                        value: true,
                        success: true,
-                       error: false,
+                       futureSuccess: true
+               },
+               willError = {
+                       error: true,
+                       futureError: true
+               },
+               willNotify = {
                        futureSuccess: true,
-                       futureError: false
+                       futureError: true,
+                       notify: true
                };
 
        jQuery.each( deferreds, function( id1, defer1 ) {
                jQuery.each( deferreds, function( id2, defer2 ) {
                        var shouldResolve = willSucceed[ id1 ] && willSucceed[ id2 ],
+                               shouldError = willError[ id1 ] || willError[ id2 ],
+                               shouldNotify = willNotify[ id1 ] || willNotify[ id2 ],
                                expected = shouldResolve ? [ 1, 1 ] : [ 0, undefined ],
-                               code = id1 + "/" + id2;
-                       jQuery.when( defer1, defer2 ).done(function( a, b ) {
+                           expectedNotify = shouldNotify && [ willNotify[ id1 ], willNotify[ id2 ] ],
+                           code = id1 + "/" + id2;
+
+                       var promise = jQuery.when( defer1, defer2 ).done(function( a, b ) {
                                if ( shouldResolve ) {
                                        same( [ a, b ], expected, code + " => resolve" );
+                               } else {
+                                       ok( false ,  code + " => resolve" );
                                }
                        }).fail(function( a, b ) {
-                               if ( !shouldResolve ) {
-                                       same( [ a, b ], expected, code + " => resolve" );
+                               if ( shouldError ) {
+                                       same( [ a, b ], expected, code + " => reject" );
+                               } else {
+                                       ok( false ,  code + " => reject" );
                                }
+                       }).progress(function progress( a, b ) {
+                               same( [ a, b ], expectedNotify, code + " => progress" );
                        });
                } );
        } );
diff --git a/test/unit/topic.js b/test/unit/topic.js
new file mode 100644 (file)
index 0000000..0b126fe
--- /dev/null
@@ -0,0 +1,68 @@
+module("topic", { teardown: moduleTeardown });
+
+test( "jQuery.Topic - Anonymous Topic", function() {
+
+       expect( 4 );
+
+       var topic = jQuery.Topic(),
+               count = 0;
+
+       function firstCallback( value ) {
+               strictEqual( count, 1, "Callback called when needed" );
+               strictEqual( value, "test", "Published value received" );
+       }
+
+       count++;
+       topic.subscribe( firstCallback );
+       topic.publish( "test" );
+       topic.unsubscribe( firstCallback );
+       count++;
+       topic.subscribe(function( value ) {
+               strictEqual( count, 2, "Callback called when needed" );
+               strictEqual( value, "test", "Published value received" );
+       });
+       topic.publish( "test" );
+
+});
+
+test( "jQuery.Topic - Named Topic", function() {
+
+       expect( 2 );
+
+       function callback( value ) {
+               ok( true, "Callback called" );
+               strictEqual( value, "test", "Proper value received" );
+       }
+
+       jQuery.Topic( "test" ).subscribe( callback );
+       jQuery.Topic( "test" ).publish( "test" );
+       jQuery.Topic( "test" ).unsubscribe( callback );
+       jQuery.Topic( "test" ).publish( "test" );
+});
+
+test( "jQuery.Topic - Helpers", function() {
+
+       expect( 4 );
+
+       function callback( value ) {
+               ok( true, "Callback called" );
+               strictEqual( value, "test", "Proper value received" );
+       }
+
+       jQuery.subscribe( "test", callback );
+       jQuery.publish( "test" , "test" );
+       jQuery.unsubscribe( "test", callback );
+       jQuery.publish( "test" , "test" );
+
+
+       var test = true,
+               subscription = jQuery.subscribe( "test", function() {
+                       ok( test, "first callback called" );
+               }, function() {
+                       ok( test, "second callback called" );
+               });
+       jQuery.publish( "test" );
+       test = false;
+       jQuery.unsubscribe( subscription );
+       jQuery.publish( "test" );
+});