From 293e5ff59782b461699a51153d99f5c9a34e2d49 Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Wed, 24 Jul 2013 10:43:37 -0700 Subject: [PATCH 1/2] Mousemove events now fire associated mouseover/mouseout events on elements over which the mouse passes. --- jquery.simulate.js | 32 ++++++++++++++-- test/testinit.css | 8 ++-- test/unit/simulate.js | 88 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 8 deletions(-) diff --git a/jquery.simulate.js b/jquery.simulate.js index 4ac556a..64ce199 100644 --- a/jquery.simulate.js +++ b/jquery.simulate.js @@ -12,7 +12,8 @@ ;(function( $, undefined ) { var rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/; + rmouseEvent = /^(?:mouse|contextmenu)|click/, + currentElementUnderMouse; $.fn.simulate = function( type, options ) { return this.each(function() { @@ -258,10 +259,33 @@ $.extend( $.simulate.prototype, { } element.unbind( "blur", trigger ); }, 1 ); - } -}); + }, + + simulateMousemove: function() { + var elementUnderMouse; + // What element will come to be underneath the mouse after the move? + elementUnderMouse = document.elementFromPoint( this.options.clientX, this.options.clientY ); + if( elementUnderMouse !== currentElementUnderMouse ) { + if( currentElementUnderMouse ) { + // Fire mouseout on the previous element + this.simulateEvent( currentElementUnderMouse, "mouseout", this.options ); + } + + if( !$( "body" ).is( elementUnderMouse ) ) { + // Fire mouseover on the new element + this.simulateEvent( elementUnderMouse, "mouseover", this.options ); + } + } + + // Fire the mousemove event on the document + this.simulateEvent( document, "mousemove", this.options ); + + // Store the element under the mouse for the next move + currentElementUnderMouse = elementUnderMouse; + } +}); /** complex events **/ @@ -301,7 +325,7 @@ $.extend( $.simulate.prototype, { clientY: Math.round( y ) }; - this.simulateEvent( document, "mousemove", coord ); + $( document ).simulate( "mousemove", coord ); } this.simulateEvent( target, "mouseup", coord ); diff --git a/test/testinit.css b/test/testinit.css index 2748790..dfac37b 100644 --- a/test/testinit.css +++ b/test/testinit.css @@ -1,9 +1,9 @@ .top-left { position: fixed; - top: 1; - left: 1; - width: 1px; - height: 1px; + top: 0px; + left: 0px; + width: 2px; + height: 2px; margin: 0; padding: 0; border: none; diff --git a/test/unit/simulate.js b/test/unit/simulate.js index 6c88506..98c2b70 100644 --- a/test/unit/simulate.js +++ b/test/unit/simulate.js @@ -22,6 +22,94 @@ for ( ; i < keyEvents.length; i++ ) { testKeyEvent( keyEvents[ i ] ); } +module( "mousemove events" ); + +asyncTest( "fire mouseover/mouseout events on DOM elements the mouse collides with", function() { + + var moves = 4, + calls = 1, + el = jQuery("
").appendTo("#qunit-fixture"), + auditTrail = []; + + // Expect one assertion per event, and one test of the audit trail + expect( + // Once for the element having been entered + 1 + + // Once for the element having been exited + 1 + + // Once for each mouse move + moves + + // Once for the audit trail + 1 + ); + + el + // Listen to the element for mouseover events + .bind( "mouseover", function () { + ok( true, "mouseover event fired at the element" ); + + // Record the event + auditTrail.push( "call" + calls + ";mouseover" ); + }) + // Listen to the element for mouseout events + .bind( "mouseout", function () { + ok( true, "mouseout event fired at the element" ); + + // Record the event + auditTrail.push( "call" + calls + ";mouseout" ); + }); + jQuery( document ).bind( "mousemove", function() { + ok( true, "mousemove event fired at the document" ); + + // Record the event + auditTrail.push( "call" + calls + ";mousemove" ); + + if ( ++calls > moves ) { + deepEqual( auditTrail, [ + // The first move should have resulted in a simple mousemove + "call1;mousemove", + // The second move should have resulted in a mouseover on the element followed by a mousemove on the document + "call2;mouseover", + "call2;mousemove", + // The third call should have resulted in a simple mousemove + "call3;mousemove", + // The fourth call should have resulted in a mouseout on the element followed by a mousemove on the document + "call4;mouseout", + "call4;mousemove" + ]); + + // Clean up + jQuery( document ).unbind( "mousemove" ); + el.unbind( "mouseover" ).unbind( "mouseout" ); + + // Tell QUnit to give'r + start(); + } + }); + + jQuery( document ) + // Start outside the element + .simulate( "mousemove", { + clientX: 0, + clientY: 2 + }) + // Move over top of the element + .simulate( "mousemove", { + clientX: 0, + clientY: 1 + }) + // Move within the element + .simulate( "mousemove", { + clientX: 1, + clientY: 1 + }) + // Move out of the element + .simulate( "mousemove", { + clientX: 2, + clientY: 1 + }); +}); + module( "complex events" ); asyncTest( "drag moves option", function() { From 19ca015bb46b3846a1bcb3ed6eb2ac0dcc443c6c Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Wed, 24 Jul 2013 10:51:53 -0700 Subject: [PATCH 2/2] Protect against cases where document.elementFromPoint() returns null. --- jquery.simulate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.simulate.js b/jquery.simulate.js index 64ce199..652972f 100644 --- a/jquery.simulate.js +++ b/jquery.simulate.js @@ -273,7 +273,7 @@ $.extend( $.simulate.prototype, { this.simulateEvent( currentElementUnderMouse, "mouseout", this.options ); } - if( !$( "body" ).is( elementUnderMouse ) ) { + if( elementUnderMouse && !$( "body" ).is( elementUnderMouse ) ) { // Fire mouseover on the new element this.simulateEvent( elementUnderMouse, "mouseover", this.options ); }