From 2d0bc7ce72ab8d64da0506a88c8cc543ce3ceaba Mon Sep 17 00:00:00 2001 From: jaubourg Date: Thu, 3 Mar 2011 19:38:06 +0100 Subject: [PATCH] Moves Deferred-related code into a separate module. Context handling has been simplified in _Deferred.resolve and jQuery.when has been refactored for clarity (and minor optimization). --- Makefile | 1 + src/core.js | 174 +------------------------------ src/deferred.js | 169 ++++++++++++++++++++++++++++++ test/csp.php | 1 + test/data/offset/absolute.html | 1 + test/data/offset/body.html | 1 + test/data/offset/fixed.html | 1 + test/data/offset/relative.html | 1 + test/data/offset/scroll.html | 1 + test/data/offset/static.html | 1 + test/data/offset/table.html | 1 + test/index.html | 2 + test/localfile.html | 1 + test/networkerror.html | 1 + test/readywait.html | 1 + test/unit/core.js | 221 --------------------------------------- test/unit/deferred.js | 222 ++++++++++++++++++++++++++++++++++++++++ 17 files changed, 407 insertions(+), 393 deletions(-) create mode 100644 src/deferred.js create mode 100644 test/unit/deferred.js diff --git a/Makefile b/Makefile index bf41bcc..a6aae42 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ 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}/deferred.js\ ${SRC_DIR}/support.js\ ${SRC_DIR}/data.js\ ${SRC_DIR}/queue.js\ diff --git a/src/core.js b/src/core.js index 1e7250f..9312ee2 100644 --- a/src/core.js +++ b/src/core.js @@ -50,15 +50,9 @@ var jQuery = function( selector, context ) { // For matching the engine and version of the browser browserMatch, - // Has the ready events already been bound? - readyBound = false, - // The deferred used on DOM ready readyList, - // Promise methods - promiseMethods = "then done fail isResolved isRejected promise".split( " " ), - // The ready event handler DOMContentLoaded, @@ -408,11 +402,11 @@ jQuery.extend({ }, bindReady: function() { - if ( readyBound ) { + if ( readyList ) { return; } - readyBound = true; + readyList = jQuery._Deferred(); // Catch cases where $(document).ready() is called after the // browser event has already occurred. @@ -792,167 +786,6 @@ jQuery.extend({ return (new Date()).getTime(); }, - // 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; - }, - - // 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; - } - } - return this; - }, - - // resolve with this as context and given arguments - resolve: function() { - deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); - return this; - }, - - // Has this deferred been resolved? - isResolved: function() { - return !!( firing || fired ); - }, - - // Cancel - cancel: function() { - cancelled = 1; - callbacks = []; - return this; - } - }; - - return deferred; - }, - - // 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; - }, - fail: failDeferred.done, - rejectWith: failDeferred.resolveWith, - reject: failDeferred.resolve, - isRejected: failDeferred.isResolved, - // 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 ); - } - return deferred; - }, - - // Deferred helper - when: function( object ) { - var lastIndex = arguments.length, - deferred = lastIndex <= 1 && object && jQuery.isFunction( object.promise ) ? - object : - jQuery.Deferred(), - promise = deferred.promise(); - - if ( lastIndex > 1 ) { - var array = slice.call( arguments, 0 ), - count = lastIndex, - iCallback = function( index ) { - return function( value ) { - array[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( promise, array ); - } - }; - }; - while( ( lastIndex-- ) ) { - object = array[ lastIndex ]; - if ( object && jQuery.isFunction( object.promise ) ) { - object.promise().then( iCallback(lastIndex), deferred.reject ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( promise, array ); - } - } else if ( deferred !== object ) { - deferred.resolve( object ); - } - return promise; - }, - // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { @@ -991,9 +824,6 @@ jQuery.extend({ browser: {} }); -// Create readyList deferred -readyList = jQuery._Deferred(); - // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); diff --git a/src/deferred.js b/src/deferred.js new file mode 100644 index 0000000..f0d7c08 --- /dev/null +++ b/src/deferred.js @@ -0,0 +1,169 @@ +(function( jQuery ) { + +var // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".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; + }, + + // 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; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // 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; + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + // 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 ); + } + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + if ( length > 1 ) { + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return deferred.promise(); + } +}); + +})( jQuery ); diff --git a/test/csp.php b/test/csp.php index acf8f32..13c324e 100644 --- a/test/csp.php +++ b/test/csp.php @@ -6,6 +6,7 @@ CSP Test Page + diff --git a/test/data/offset/absolute.html b/test/data/offset/absolute.html index b4db30a..9d7990a 100644 --- a/test/data/offset/absolute.html +++ b/test/data/offset/absolute.html @@ -16,6 +16,7 @@ #positionTest { position: absolute; } + diff --git a/test/data/offset/body.html b/test/data/offset/body.html index e3eb4f8..8dbf282 100644 --- a/test/data/offset/body.html +++ b/test/data/offset/body.html @@ -9,6 +9,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/data/offset/fixed.html b/test/data/offset/fixed.html index f93c20f..3181718 100644 --- a/test/data/offset/fixed.html +++ b/test/data/offset/fixed.html @@ -13,6 +13,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/data/offset/relative.html b/test/data/offset/relative.html index 3586476..280a2fc 100644 --- a/test/data/offset/relative.html +++ b/test/data/offset/relative.html @@ -11,6 +11,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/data/offset/scroll.html b/test/data/offset/scroll.html index 50de95e..a0d1f4d 100644 --- a/test/data/offset/scroll.html +++ b/test/data/offset/scroll.html @@ -14,6 +14,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/data/offset/static.html b/test/data/offset/static.html index b1fb9f1..a61b6d1 100644 --- a/test/data/offset/static.html +++ b/test/data/offset/static.html @@ -11,6 +11,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/data/offset/table.html b/test/data/offset/table.html index 528e530..11fb0e7 100644 --- a/test/data/offset/table.html +++ b/test/data/offset/table.html @@ -11,6 +11,7 @@ #marker { position: absolute; border: 2px solid #000; width: 50px; height: 50px; background: #ccc; } + diff --git a/test/index.html b/test/index.html index d621315..c7c2ae5 100644 --- a/test/index.html +++ b/test/index.html @@ -9,6 +9,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/test/localfile.html b/test/localfile.html index 96e0f98..c552f21 100644 --- a/test/localfile.html +++ b/test/localfile.html @@ -5,6 +5,7 @@ jQuery Local File Test + diff --git a/test/networkerror.html b/test/networkerror.html index b06a6ba..f98bf46 100644 --- a/test/networkerror.html +++ b/test/networkerror.html @@ -16,6 +16,7 @@ div { margin-top: 10px; } + diff --git a/test/readywait.html b/test/readywait.html index 8e0d3d5..4f12476 100644 --- a/test/readywait.html +++ b/test/readywait.html @@ -15,6 +15,7 @@ #expectedOutput { background-color: green } + diff --git a/test/unit/core.js b/test/unit/core.js index 208b21c..6ee8724 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -914,227 +914,6 @@ test("jQuery.parseJSON", function(){ } }); -test("jQuery._Deferred()", function() { - - expect( 11 ); - - var deferred, - object, - test; - - deferred = jQuery._Deferred(); - - 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 = jQuery._Deferred().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 = jQuery._Deferred().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 = jQuery._Deferred(); - - deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { - ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); - }); - - // #8421 - deferred = jQuery._Deferred(); - deferred.resolveWith().done(function() { - ok( true, "Test resolveWith can be called with no argument" ); - }); -}); - -test("jQuery.Deferred()", function() { - - expect( 10 ); - - jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument" ); - this.resolve( "done" ); - }).then( function( value ) { - strictEqual( value , "done" , "Passed function executed" ); - }); - - jQuery.Deferred().resolve().then( function() { - ok( true , "Success on resolve" ); - }, function() { - ok( false , "Error on resolve" ); - }); - - jQuery.Deferred().reject().then( function() { - ok( false , "Success on reject" ); - }, function() { - ok( true , "Error on reject" ); - }); - - ( new jQuery.Deferred( function( defer ) { - strictEqual( this , defer , "Defer passed as this & first argument (new)" ); - this.resolve( "done" ); - }) ).then( function( value ) { - strictEqual( value , "done" , "Passed function executed (new)" ); - }); - - ( new jQuery.Deferred() ).resolve().then( function() { - ok( true , "Success on resolve (new)" ); - }, function() { - ok( false , "Error on resolve (new)" ); - }); - - ( new jQuery.Deferred() ).reject().then( function() { - ok( false , "Success on reject (new)" ); - }, function() { - ok( true , "Error on reject (new)" ); - }); - - var tmp = jQuery.Deferred(); - - strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); - strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); -}); - -test("jQuery.when()", function() { - - expect( 23 ); - - // Some other objects - jQuery.each( { - - "an empty string": "", - "a non-empty string": "some string", - "zero": 0, - "a number other than zero": 1, - "true": true, - "false": false, - "null": null, - "undefined": undefined, - "a plain object": {} - - } , function( message , value ) { - - ok( jQuery.isFunction( jQuery.when( value ).then( function( resolveValue ) { - strictEqual( resolveValue , value , "Test the promise was resolved with " + message ); - } ).promise ) , "Test " + message + " triggers the creation of a new Promise" ); - - } ); - - ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { - strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); - } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); - - var cache, i; - - for( i = 1 ; i < 4 ; i++ ) { - jQuery.when( cache || jQuery.Deferred( function() { - this.resolve( i ); - }) ).then( function( value ) { - strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); - cache = value; - }, function() { - ok( false , "Fail called" ); - }); - } -}); - -test("jQuery.when() - joined", function() { - - expect(8); - - jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { - strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); - strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); - strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); - }).fail( function() { - ok( false , "Test the created deferred was resolved - non-observables"); - }); - - var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), - errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); - - jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { - strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); - same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); - strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); - }).fail( function() { - ok( false , "Test the created deferred was resolved - resolved observable"); - }); - - jQuery.when( 1 , errorDeferred , 3 ).done( function() { - ok( false , "Test the created deferred was rejected - rejected observable"); - }).fail( function( error , errorParam ) { - strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); - strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); - }); -}); - test("jQuery.sub() - Static Methods", function(){ expect(18); var Subclass = jQuery.sub(); diff --git a/test/unit/deferred.js b/test/unit/deferred.js new file mode 100644 index 0000000..6ba4767 --- /dev/null +++ b/test/unit/deferred.js @@ -0,0 +1,222 @@ +module("deferred", { teardown: moduleTeardown }); + +test("jQuery._Deferred()", function() { + + expect( 11 ); + + var deferred, + object, + test; + + deferred = jQuery._Deferred(); + + 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 = jQuery._Deferred().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 = jQuery._Deferred().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 = jQuery._Deferred(); + + deferred.resolveWith( jQuery , [ document ] ).done( function( doc ) { + ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); + }); + + // #8421 + deferred = jQuery._Deferred(); + deferred.resolveWith().done(function() { + ok( true, "Test resolveWith can be called with no argument" ); + }); +}); + +test("jQuery.Deferred()", function() { + + expect( 10 ); + + jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument" ); + this.resolve( "done" ); + }).then( function( value ) { + strictEqual( value , "done" , "Passed function executed" ); + }); + + jQuery.Deferred().resolve().then( function() { + ok( true , "Success on resolve" ); + }, function() { + ok( false , "Error on resolve" ); + }); + + jQuery.Deferred().reject().then( function() { + ok( false , "Success on reject" ); + }, function() { + ok( true , "Error on reject" ); + }); + + ( new jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument (new)" ); + this.resolve( "done" ); + }) ).then( function( value ) { + strictEqual( value , "done" , "Passed function executed (new)" ); + }); + + ( new jQuery.Deferred() ).resolve().then( function() { + ok( true , "Success on resolve (new)" ); + }, function() { + ok( false , "Error on resolve (new)" ); + }); + + ( new jQuery.Deferred() ).reject().then( function() { + ok( false , "Success on reject (new)" ); + }, function() { + ok( true , "Error on reject (new)" ); + }); + + var tmp = jQuery.Deferred(); + + strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); +}); + +test("jQuery.when()", function() { + + expect( 23 ); + + // Some other objects + jQuery.each( { + + "an empty string": "", + "a non-empty string": "some string", + "zero": 0, + "a number other than zero": 1, + "true": true, + "false": false, + "null": null, + "undefined": undefined, + "a plain object": {} + + } , function( message , value ) { + + ok( jQuery.isFunction( jQuery.when( value ).then( function( resolveValue ) { + strictEqual( resolveValue , value , "Test the promise was resolved with " + message ); + } ).promise ) , "Test " + message + " triggers the creation of a new Promise" ); + + } ); + + ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); + } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + + var cache, i; + + for( i = 1 ; i < 4 ; i++ ) { + jQuery.when( cache || jQuery.Deferred( function() { + this.resolve( i ); + }) ).then( function( value ) { + strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); + cache = value; + }, function() { + ok( false , "Fail called" ); + }); + } +}); + +test("jQuery.when() - joined", function() { + + expect(8); + + jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); + strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); + strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - non-observables"); + }); + + var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), + errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); + + jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); + same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); + strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - resolved observable"); + }); + + jQuery.when( 1 , errorDeferred , 3 ).done( function() { + ok( false , "Test the created deferred was rejected - rejected observable"); + }).fail( function( error , errorParam ) { + strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); + strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); + }); +}); -- 1.7.10.4