From b3b2d6c3dd51fbdc69e1942e9af75cc99a1834c2 Mon Sep 17 00:00:00 2001 From: Corey Frang Date: Tue, 19 May 2015 17:48:42 -0400 Subject: [PATCH] Effects: Adding unit tests for jQuery.Animation Closes gh-2326 --- Gruntfile.js | 5 +- src/effects.js | 44 ++++---- test/data/testinit.js | 1 + test/unit/animation.js | 230 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 test/unit/animation.js diff --git a/Gruntfile.js b/Gruntfile.js index 8c88370e4..1a6bdac1a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -103,14 +103,15 @@ module.exports = function( grunt ) { src: "src/**/*.js", gruntfile: "Gruntfile.js", - // Right now, check only test helpers - test: [ "test/data/testrunner.js", "test/unit/tween.js" ], + // Check parts of tests that pass + test: [ "test/data/testrunner.js", "test/unit/animation.js", "test/unit/tween.js" ], release: [ "build/*.js", "!build/release-notes.js" ], tasks: "build/tasks/*.js" }, testswarm: { tests: [ "ajax", + "animation", "attributes", "callbacks", "core", diff --git a/src/effects.js b/src/effects.js index 53b73fd84..40da88705 100644 --- a/src/effects.js +++ b/src/effects.js @@ -2,6 +2,7 @@ define([ "./core", "./var/document", "./var/rcssNum", + "./var/rnotwhite", "./css/var/cssExpand", "./css/var/isHidden", "./css/var/swap", @@ -16,20 +17,13 @@ define([ "./manipulation", "./css", "./effects/Tween" -], function( jQuery, document, rcssNum, cssExpand, isHidden, swap, adjustCSS, dataPriv, showHide ) { +], function( jQuery, document, rcssNum, rnotwhite, cssExpand, isHidden, swap, + adjustCSS, dataPriv, showHide ) { var fxNow, timerId, rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/, - animationPrefilters = [ defaultPrefilter ], - tweeners = { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }; + rrun = /queueHooks$/; function raf() { if ( timerId ) { @@ -69,7 +63,7 @@ function genFx( type, includeWidth ) { function createTween( value, prop, animation ) { var tween, - collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { @@ -281,7 +275,7 @@ function Animation( elem, properties, options ) { var result, stopped, index = 0, - length = animationPrefilters.length, + length = Animation.prefilters.length, deferred = jQuery.Deferred().always( function() { // Don't match elem in the :animated selector delete tick.elem; @@ -357,8 +351,12 @@ function Animation( elem, properties, options ) { propFilter( props, animation.opts.specialEasing ); for ( ; index < length ; index++ ) { - result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } return result; } } @@ -386,12 +384,20 @@ function Animation( elem, properties, options ) { jQuery.Animation = jQuery.extend( Animation, { + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + tweener: function( props, callback ) { if ( jQuery.isFunction( props ) ) { callback = props; props = [ "*" ]; } else { - props = props.split(" "); + props = props.match( rnotwhite ); } var prop, @@ -400,16 +406,18 @@ jQuery.Animation = jQuery.extend( Animation, { for ( ; index < length ; index++ ) { prop = props[ index ]; - tweeners[ prop ] = tweeners[ prop ] || []; - tweeners[ prop ].unshift( callback ); + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); } }, + prefilters: [ defaultPrefilter ], + prefilter: function( callback, prepend ) { if ( prepend ) { - animationPrefilters.unshift( callback ); + Animation.prefilters.unshift( callback ); } else { - animationPrefilters.push( callback ); + Animation.prefilters.push( callback ); } } }); diff --git a/test/data/testinit.js b/test/data/testinit.js index e59d9db32..ef90fe45c 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -286,6 +286,7 @@ this.loadTests = function() { "unit/effects.js", "unit/offset.js", "unit/dimensions.js", + "unit/animation.js", "unit/tween.js" ]; diff --git a/test/unit/animation.js b/test/unit/animation.js new file mode 100644 index 000000000..6bb070145 --- /dev/null +++ b/test/unit/animation.js @@ -0,0 +1,230 @@ +( function() { + +// Can't test what ain't there +if ( !jQuery.fx ) { + return; +} + +var oldRaf = window.requestAnimationFrame, + defaultPrefilter = jQuery.Animation.prefilters[ 0 ], + defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ], + startTime = 505877050; + +// This module tests jQuery.Animation and the corresponding 1.8+ effects APIs +module( "animation", { + setup: function() { + window.requestAnimationFrame = null; + this.sandbox = sinon.sandbox.create(); + this.clock = this.sandbox.useFakeTimers( startTime ); + this._oldInterval = jQuery.fx.interval; + jQuery.fx.step = {}; + jQuery.fx.interval = 10; + jQuery.now = Date.now; + jQuery.Animation.prefilters = [ defaultPrefilter ]; + jQuery.Animation.tweeners = { "*": [ defaultTweener ] }; + }, + teardown: function() { + this.sandbox.restore(); + jQuery.now = Date.now; + jQuery.fx.stop(); + jQuery.fx.interval = this._oldInterval; + window.requestAnimationFrame = oldRaf; + return moduleTeardown.apply( this, arguments ); + } +} ); + +test( "Animation( subject, props, opts ) - shape", function() { + expect( 20 ); + + var subject = { test: 0 }, + props = { test: 1 }, + opts = { queue: "fx", duration: 100 }, + animation = jQuery.Animation( subject, props, opts ); + + equal( animation.elem, subject, ".elem is set to the exact object passed" ); + equal( animation.originalOptions, opts, ".originalOptions is set to options passed" ); + equal( animation.originalProperties, props, ".originalProperties is set to props passed" ); + + notEqual( animation.props, props, ".props is not the original however" ); + deepEqual( animation.props, props, ".props is a copy of the original" ); + + deepEqual( animation.opts, { + duration: 100, + queue: "fx", + specialEasing: { test: undefined }, + easing: jQuery.easing._default + }, ".options is filled with default easing and specialEasing" ); + + equal( animation.startTime, startTime, "startTime was set" ); + equal( animation.duration, 100, ".duration is set" ); + + equal( animation.tweens.length, 1, ".tweens has one Tween" ); + equal( typeof animation.tweens[ 0 ].run, "function", "which has a .run function" ); + + equal( typeof animation.createTween, "function", ".createTween is a function" ); + equal( typeof animation.stop, "function", ".stop is a function" ); + + equal( typeof animation.done, "function", ".done is a function" ); + equal( typeof animation.fail, "function", ".fail is a function" ); + equal( typeof animation.always, "function", ".always is a function" ); + equal( typeof animation.progress, "function", ".progress is a function" ); + + equal( jQuery.timers.length, 1, "Added a timers function" ); + equal( jQuery.timers[ 0 ].elem, subject, "...with .elem as the subject" ); + equal( jQuery.timers[ 0 ].anim, animation, "...with .anim as the animation" ); + equal( jQuery.timers[ 0 ].queue, opts.queue, "...with .queue" ); + + // Cleanup after ourselves by ticking to the end + this.clock.tick( 100 ); +} ); + +test( "Animation.prefilter( fn ) - calls prefilter after defaultPrefilter", function() { + expect( 1 ); + + var prefilter = this.sandbox.stub(), + defaultSpy = this.sandbox.spy( jQuery.Animation.prefilters, 0 ); + + jQuery.Animation.prefilter( prefilter ); + + jQuery.Animation( {}, {}, {} ); + ok( prefilter.calledAfter( defaultSpy ), "our prefilter called after" ); +} ); + +test( "Animation.prefilter( fn, true ) - calls prefilter before defaultPrefilter", function() { + expect( 1 ); + + var prefilter = this.sandbox.stub(), + defaultSpy = this.sandbox.spy( jQuery.Animation.prefilters, 0 ); + + jQuery.Animation.prefilter( prefilter, true ); + + jQuery.Animation( {}, {}, {} ); + ok( prefilter.calledBefore( defaultSpy ), "our prefilter called before" ); +} ); + +test( "Animation.prefilter - prefilter return hooks", function() { + expect( 34 ); + + var animation, realAnimation, element, + sandbox = this.sandbox, + ourAnimation = { stop: this.sandbox.spy() }, + target = { height: 50 }, + props = { height: 100 }, + opts = { duration: 100 }, + prefilter = this.sandbox.spy( function() { + realAnimation = this; + sandbox.spy( realAnimation, "createTween" ); + + deepEqual( realAnimation.originalProperties, props, "originalProperties" ); + equal( arguments[ 0 ], this.elem, "first param elem" ); + equal( arguments[ 1 ], this.props, "second param props" ); + equal( arguments[ 2 ], this.opts, "third param opts" ); + return ourAnimation; + } ), + defaultSpy = sandbox.spy( jQuery.Animation.prefilters, 0 ), + queueSpy = sandbox.spy( function( next ) { + next(); + } ), + TweenSpy = sandbox.spy( jQuery, "Tween" ); + + jQuery.Animation.prefilter( prefilter, true ); + + sandbox.stub( jQuery.fx, "timer" ); + + animation = jQuery.Animation( target, props, opts ); + + equal( prefilter.callCount, 1, "Called prefilter" ); + + equal( defaultSpy.callCount, 0, + "Returning something from a prefilter caused remaining prefilters to not run" ); + equal( jQuery.fx.timer.callCount, 0, "Returning something never queues a timer" ); + equal( animation, ourAnimation, "Returning something returned it from jQuery.Animation" ); + equal( realAnimation.createTween.callCount, 0, "Returning something never creates tweens" ); + equal( TweenSpy.callCount, 0, "Returning something never creates tweens" ); + + // Test overriden usage on queues: + prefilter.reset(); + element = jQuery( "
" ) + .css( "height", 50 ) + .animate( props, 100 ) + .queue( queueSpy ) + .animate( props, 100 ) + .queue( queueSpy ) + .animate( props, 100 ) + .queue( queueSpy ); + + equal( prefilter.callCount, 1, "Called prefilter" ); + equal( queueSpy.callCount, 0, "Next function in queue not called" ); + + realAnimation.opts.complete.call( realAnimation.elem ); + equal( queueSpy.callCount, 1, "Next function in queue called after complete" ); + + equal( prefilter.callCount, 2, "Called prefilter again - animation #2" ); + equal( ourAnimation.stop.callCount, 0, ".stop() on our animation hasn't been called" ); + + element.stop(); + equal( ourAnimation.stop.callCount, 1, ".stop() called ourAnimation.stop()" ); + ok( !ourAnimation.stop.args[ 0 ][ 0 ], ".stop( falsy ) (undefined or false are both valid)" ); + + equal( queueSpy.callCount, 2, "Next queue function called" ); + ok( queueSpy.calledAfter( ourAnimation.stop ), "After our animation was told to stop" ); + + // ourAnimation.stop.reset(); + equal( prefilter.callCount, 3, "Got the next animation" ); + + ourAnimation.stop.reset(); + + // do not clear queue, gotoEnd + element.stop( false, true ); + ok( ourAnimation.stop.calledWith( true ), ".stop(true) calls .stop(true)" ); + ok( queueSpy.calledAfter( ourAnimation.stop ), + "and the next queue function ran after we were told" ); +} ); + +test( "Animation.tweener( fn ) - unshifts a * tweener", function() { + expect( 2 ); + var starTweeners = jQuery.Animation.tweeners[ "*" ]; + + jQuery.Animation.tweener( jQuery.noop ); + equal( starTweeners.length, 2 ); + deepEqual( starTweeners, [ jQuery.noop, defaultTweener ] ); +} ); + +test( "Animation.tweener( 'prop', fn ) - unshifts a 'prop' tweener", function() { + expect( 4 ); + var tweeners = jQuery.Animation.tweeners, + fn = function() {}; + + jQuery.Animation.tweener( "prop", jQuery.noop ); + equal( tweeners.prop.length, 1 ); + deepEqual( tweeners.prop, [ jQuery.noop ] ); + + jQuery.Animation.tweener( "prop", fn ); + equal( tweeners.prop.length, 2 ); + deepEqual( tweeners.prop, [ fn, jQuery.noop ] ); +} ); + +test( "Animation.tweener( 'list of props', fn ) - unshifts a tweener to each prop", function() { + expect( 2 ); + var tweeners = jQuery.Animation.tweeners, + fn = function() {}; + + jQuery.Animation.tweener( "list of props", jQuery.noop ); + deepEqual( tweeners, { + list: [ jQuery.noop ], + of: [ jQuery.noop ], + props: [ jQuery.noop ], + "*": [ defaultTweener ] + } ); + + // Test with extra whitespaces + jQuery.Animation.tweener( " list\t of \tprops\n*", fn ); + deepEqual( tweeners, { + list: [ fn, jQuery.noop ], + of: [ fn, jQuery.noop ], + props: [ fn, jQuery.noop ], + "*": [ fn, defaultTweener ] + } ); +} ); + +} )();