QUnit.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 XX", "once": "XABC X X X X X XABA X XX", "memory": "XABC XABC XABCABCCC XA XBB XB XABA XC XX", "unique": "XABC X XABCA X XBB X XAB X X", "stopOnFalse": "XABC X XABCABCC X XBB X XA X XX", "once memory": "XABC XABC X XA X XA XABA XC XX", "once unique": "XABC X X X X X XAB X X", "once stopOnFalse": "XABC X X X X X XA X XX", "memory unique": "XABC XA XABCA XA XBB XB XAB XC X", "memory stopOnFalse": "XABC XABC XABCABCCC XA XBB XB XA X XX", "unique stopOnFalse": "XABC X XABCA X XBB X XA X X" }, filters = { "no filter": undefined, "filter": function( fn ) { return function() { return fn.apply( this, arguments ); }; } }; function showFlags( flags ) { if ( typeof flags === "string" ) { return "'" + flags + "'"; } var output = [], key; for ( key in flags ) { output.push( "'" + key + "': " + flags[ key ] ); } return "{ " + output.join( ", " ) + " }"; } jQuery.each( tests, function( strFlags, resultString ) { var objectFlags = {}; jQuery.each( strFlags.split( " " ), function() { if ( this.length ) { objectFlags[ this ] = true; } } ); jQuery.each( filters, function( filterLabel ) { jQuery.each( { "string": strFlags, "object": objectFlags }, function( flagsTypes, flags ) { QUnit.test( "jQuery.Callbacks( " + showFlags( flags ) + " ) - " + filterLabel, function( assert ) { assert.expect( 29 ); var cblist, results = resultString.split( /\s+/ ); // Basic binding and firing output = "X"; cblist = jQuery.Callbacks( flags ); assert.strictEqual( cblist.locked(), false, ".locked() initially false" ); assert.strictEqual( cblist.disabled(), false, ".disabled() initially false" ); assert.strictEqual( cblist.fired(), false, ".fired() initially false" ); cblist.add( function( str ) { output += str; } ); assert.strictEqual( cblist.fired(), false, ".fired() still false after .add" ); cblist.fire( "A" ); assert.strictEqual( output, "XA", "Basic binding and firing" ); assert.strictEqual( cblist.fired(), true, ".fired() detects firing" ); output = "X"; cblist.disable(); cblist.add( function( str ) { output += str; } ); assert.strictEqual( output, "X", "Adding a callback after disabling" ); cblist.fire( "A" ); assert.strictEqual( output, "X", "Firing after disabling" ); assert.strictEqual( cblist.disabled(), true, ".disabled() becomes true" ); assert.strictEqual( cblist.locked(), true, "disabling locks" ); // Emptying while firing (#13517) cblist = jQuery.Callbacks( flags ); cblist.add( cblist.empty ); cblist.add( function() { assert.ok( false, "not emptied" ); } ); cblist.fire(); // Disabling while firing cblist = jQuery.Callbacks( flags ); cblist.add( cblist.disable ); cblist.add( function() { assert.ok( false, "not disabled" ); } ); cblist.fire(); // Basic binding and firing (context, arguments) output = "X"; cblist = jQuery.Callbacks( flags ); cblist.add( function() { assert.equal( this, window, "Basic binding and firing (context)" ); output += Array.prototype.join.call( arguments, "" ); } ); cblist.fireWith( window, [ "A", "B" ] ); assert.strictEqual( output, "XAB", "Basic binding and firing (arguments)" ); // fireWith with no arguments output = ""; cblist = jQuery.Callbacks( flags ); cblist.add( function() { assert.equal( this, window, "fireWith with no arguments (context is window)" ); assert.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(); assert.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(); assert.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; } ); assert.strictEqual( output, "X", "Lock early" ); assert.strictEqual( cblist.locked(), true, "Locking reflected in accessor" ); // Locking while firing (gh-1990) output = "X"; cblist = jQuery.Callbacks( flags ); cblist.add( cblist.lock ); cblist.add( function( str ) { output += str; } ); cblist.fire( "A" ); assert.strictEqual( output, "XA", "Locking doesn't abort execution (gh-1990)" ); // Ordering output = "X"; cblist = jQuery.Callbacks( flags ); cblist.add( function() { cblist.add( outputC ); outputA(); }, outputB ); cblist.fire(); assert.strictEqual( output, results.shift(), "Proper ordering" ); // Add and fire again output = "X"; cblist.add( function() { cblist.add( outputC ); outputA(); }, outputB ); assert.strictEqual( output, results.shift(), "Add after fire" ); output = "X"; cblist.fire(); assert.strictEqual( output, results.shift(), "Fire again" ); // Multiple fire output = "X"; cblist = jQuery.Callbacks( flags ); cblist.add( function( str ) { output += str; } ); cblist.fire( "A" ); assert.strictEqual( output, "XA", "Multiple fire (first fire)" ); output = "X"; cblist.add( function( str ) { output += str; } ); assert.strictEqual( output, results.shift(), "Multiple fire (first new callback)" ); output = "X"; cblist.fire( "B" ); assert.strictEqual( output, results.shift(), "Multiple fire (second fire)" ); output = "X"; cblist.add( function( str ) { output += str; } ); assert.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(); assert.strictEqual( output, results.shift(), "Callback returning false" ); // Add another callback (to control lists with memory do not fire anymore) output = "X"; cblist.add( outputC ); assert.strictEqual( output, results.shift(), "Adding a callback after one returned false" ); // Callbacks are not iterated output = ""; function handler() { output += "X"; } handler.method = function() { output += "!"; }; cblist = jQuery.Callbacks( flags ); cblist.add( handler ); cblist.add( handler ); cblist.fire(); assert.strictEqual( output, results.shift(), "No callback iteration" ); } ); } ); } ); } ); } )(); QUnit.test( "jQuery.Callbacks( options ) - options are copied", function( assert ) { assert.expect( 1 ); var options = { "unique": true }, cb = jQuery.Callbacks( options ), count = 0, fn = function() { assert.ok( !( count++ ), "called once" ); }; options[ "unique" ] = false; cb.add( fn, fn ); cb.fire(); } ); QUnit.test( "jQuery.Callbacks.fireWith - arguments are copied", function( assert ) { assert.expect( 1 ); var cb = jQuery.Callbacks( "memory" ), args = [ "hello" ]; cb.fireWith( null, args ); args[ 0 ] = "world"; cb.add( function( hello ) { assert.strictEqual( hello, "hello", "arguments are copied internally" ); } ); } ); QUnit.test( "jQuery.Callbacks.remove - should remove all instances", function( assert ) { assert.expect( 1 ); var cb = jQuery.Callbacks(); function fn() { assert.ok( false, "function wasn't removed" ); } cb.add( fn, fn, function() { assert.ok( true, "end of test" ); } ).remove( fn ).fire(); } ); QUnit.test( "jQuery.Callbacks.has", function( assert ) { assert.expect( 13 ); var cb = jQuery.Callbacks(); function getA() { return "A"; } function getB() { return "B"; } function getC() { return "C"; } cb.add( getA, getB, getC ); assert.strictEqual( cb.has(), true, "No arguments to .has() returns whether callback function(s) are attached or not" ); assert.strictEqual( cb.has( getA ), true, "Check if a specific callback function is in the Callbacks list" ); cb.remove( getB ); assert.strictEqual( cb.has( getB ), false, "Remove a specific callback function and make sure its no longer there" ); assert.strictEqual( cb.has( getA ), true, "Remove a specific callback function and make sure other callback function is still there" ); cb.empty(); assert.strictEqual( cb.has(), false, "Empty list and make sure there are no callback function(s)" ); assert.strictEqual( cb.has( getA ), false, "Check for a specific function in an empty() list" ); cb.add( getA, getB, function() { assert.strictEqual( cb.has(), true, "Check if list has callback function(s) from within a callback function" ); assert.strictEqual( cb.has( getA ), true, "Check if list has a specific callback from within a callback function" ); } ).fire(); assert.strictEqual( cb.has(), true, "Callbacks list has callback function(s) after firing" ); cb.disable(); assert.strictEqual( cb.has(), false, "disabled() list has no callback functions (returns false)" ); assert.strictEqual( cb.has( getA ), false, "Check for a specific function in a disabled() list" ); cb = jQuery.Callbacks( "unique" ); cb.add( getA ); cb.add( getA ); assert.strictEqual( cb.has(), true, "Check if unique list has callback function(s) attached" ); cb.lock(); assert.strictEqual( cb.has(), false, "locked() list is empty and returns false" ); } ); QUnit.test( "jQuery.Callbacks() - adding a string doesn't cause a stack overflow", function( assert ) { assert.expect( 1 ); jQuery.Callbacks().add( "hello world" ); assert.ok( true, "no stack overflow" ); } ); QUnit.test( "jQuery.Callbacks() - disabled callback doesn't fire (gh-1790)", function( assert ) { assert.expect( 1 ); var cb = jQuery.Callbacks(), fired = false, shot = function() { fired = true; }; cb.disable(); cb.empty(); cb.add( shot ); cb.fire(); assert.ok( !fired, "Disabled callback function didn't fire" ); } );