diff options
author | Michał Gołębiowski-Owczarek <m.goleb@gmail.com> | 2023-03-27 21:22:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-27 21:22:38 +0200 |
commit | ce60d31893deab7d3da592b5173e90b5d50e7732 (patch) | |
tree | e1373262623de3b80867669f397513e48246c974 /test/unit | |
parent | 992a1911d0b6195012edc25fd5a48810d4be64b5 (diff) | |
download | jquery-ce60d31893deab7d3da592b5173e90b5d50e7732.tar.gz jquery-ce60d31893deab7d3da592b5173e90b5d50e7732.zip |
Event: Simulate focus/blur in IE via focusin/focusout
In IE (all versions), `focus` & `blur` handlers are fired asynchronously
but `focusin` & `focusout` are run synchronously. In other browsers, all
those handlers are fired synchronously. Asynchronous behavior of these
handlers in IE caused issues for IE (gh-4856, gh-4859).
We now simulate `focus` via `focusin` & `blur` via `focusout` in IE to avoid
these issues. This also let us simplify some tests.
This commit also simplifies `leverageNative` - with IE now using `focusin`
to simulate `focus` and `focusout` to simulate `blur`, we don't have to deal
with async events in `leverageNative`. This also fixes broken `focus` triggers
after first triggering it on a hidden element - previously, `leverageNative`
assumed that the native `focus` handler not firing after calling the native
`focus` method meant it would be handled later, asynchronously, which
was not the case (gh-4950).
Fixes gh-4856
Fixes gh-4859
Fixes gh-4950
Closes gh-5223
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
Diffstat (limited to 'test/unit')
-rw-r--r-- | test/unit/event.js | 322 |
1 files changed, 202 insertions, 120 deletions
diff --git a/test/unit/event.js b/test/unit/event.js index 8ca06ced8..aa55c06a1 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2172,12 +2172,12 @@ QUnit.test( "focusin bubbles", function( assert ) { // Removed since DOM focus is unreliable on test swarm // DOM focus method -// input[0].focus(); +// input[ 0 ].focus(); // To make the next focus test work, we need to take focus off the input. // This will fire another focusin event, so set order to reflect that. // order = 1; -// jQuery("#text1")[0].focus(); +// jQuery( "#text1" )[ 0 ].focus(); // jQuery trigger, which calls DOM focus order = 0; @@ -2187,6 +2187,42 @@ QUnit.test( "focusin bubbles", function( assert ) { jQuery( "body" ).off( "focusin.focusinBubblesTest" ); } ); +QUnit.test( "focus does not bubble", function( assert ) { + assert.expect( 1 ); + + var done = assert.async(), + input = jQuery( "<input type='text' />" ).prependTo( "body" ); + + // focus the element so DOM focus won't fire + input[ 0 ].focus(); + + jQuery( "body" ).on( "focus.focusDoesNotBubbleTest", function() { + assert.ok( false, "focus doesn't fire on body" ); + } ); + + input.on( "focus.focusDoesNotBubbleTest", function() { + assert.ok( true, "focus on the element" ); + } ); + +// Removed since DOM focus is unreliable on test swarm + // DOM focus method +// input[ 0 ].focus(); + + // To make the next focus test work, we need to take focus off the input. + // This will fire another focusin event, so set order to reflect that. +// jQuery( "#text1" )[ 0 ].focus(); + + // jQuery trigger, which calls DOM focus + input.trigger( "focus" ); + + input.remove(); + jQuery( "body" ).off( "focus.focusDoesNotBubbleTest" ); + + setTimeout( function() { + done(); + }, 50 ); +} ); + QUnit.test( "custom events with colons (trac-3533, trac-8272)", function( assert ) { assert.expect( 1 ); @@ -2652,6 +2688,10 @@ QUnit.test( "element removed during focusout (gh-4417)", function( assert ) { button[ 0 ].blur = function() { jQuery.cleanData( [ this ] ); this.parentNode.removeChild( this ); + + // Redefine `blur` to avoid a hard crash in Karma tests that stop + // the test runner in case this test fails. + this.blur = jQuery.noop; }; button[ 0 ].click(); @@ -3067,12 +3107,7 @@ QUnit.test( "focusout/focusin support", function( assert ) { var focus, parent = jQuery( "<div>" ), input = jQuery( "<input>" ), - inputExternal = jQuery( "<input>" ), - - // Support: IE <=9 - 11+ - // focus and blur events are asynchronous; this is the resulting mess. - // The browser window must be topmost for this to work properly!! - done = assert.async(); + inputExternal = jQuery( "<input>" ); parent.append( input ); jQuery( "#qunit-fixture" ).append( parent ).append( inputExternal ); @@ -3080,61 +3115,54 @@ QUnit.test( "focusout/focusin support", function( assert ) { // initially, lose focus inputExternal[ 0 ].focus(); - setTimeout( function() { - parent - .on( "focus", function() { - assert.ok( false, "parent: focus not fired" ); - } ) - .on( "focusin", function() { - assert.ok( true, "parent: focusin fired" ); - } ) - .on( "blur", function() { - assert.ok( false, "parent: blur not fired" ); - } ) - .on( "focusout", function() { - assert.ok( true, "parent: focusout fired" ); - } ); - - input - .on( "focus", function() { - assert.ok( true, "element: focus fired" ); - } ) - .on( "focusin", function() { - assert.ok( true, "element: focusin fired" ); - focus = true; - } ) - .on( "blur", function() { - assert.ok( true, "parent: blur fired" ); - } ) - .on( "focusout", function() { - assert.ok( true, "element: focusout fired" ); - } ); - - // gain focus - input[ 0 ].focus(); + parent + .on( "focus", function() { + assert.ok( false, "parent: focus not fired" ); + } ) + .on( "focusin", function() { + assert.ok( true, "parent: focusin fired" ); + } ) + .on( "blur", function() { + assert.ok( false, "parent: blur not fired" ); + } ) + .on( "focusout", function() { + assert.ok( true, "parent: focusout fired" ); + } ); - // then lose it - inputExternal[ 0 ].focus(); + input + .on( "focus", function() { + assert.ok( true, "element: focus fired" ); + } ) + .on( "focusin", function() { + assert.ok( true, "element: focusin fired" ); + focus = true; + } ) + .on( "blur", function() { + assert.ok( true, "parent: blur fired" ); + } ) + .on( "focusout", function() { + assert.ok( true, "element: focusout fired" ); + } ); - setTimeout( function() { + // gain focus + input[ 0 ].focus(); - // DOM focus is unreliable in TestSwarm - if ( QUnit.isSwarm && !focus ) { - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - } + // then lose it + inputExternal[ 0 ].focus(); - // cleanup - parent.off(); - input.off(); + // DOM focus is unreliable in TestSwarm + if ( QUnit.isSwarm && !focus ) { + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + } - done(); - }, 50 ); - }, 50 ); + // cleanup + parent.off(); + input.off(); } ); QUnit.test( "focus-blur order (trac-12868)", function( assert ) { @@ -3142,56 +3170,45 @@ QUnit.test( "focus-blur order (trac-12868)", function( assert ) { var order, $text = jQuery( "#text1" ), - $radio = jQuery( "#radio1" ), - - // Support: IE <=9 - 11+ - // focus and blur events are asynchronous; this is the resulting mess. - // The browser window must be topmost for this to work properly!! - done = assert.async(); + $radio = jQuery( "#radio1" ); $radio[ 0 ].focus(); - setTimeout( function() { - - $text - .on( "focus", function() { - assert.equal( order++, 1, "text focus" ); - } ) - .on( "blur", function() { - assert.equal( order++, 0, "text blur" ); - } ); - $radio - .on( "focus", function() { - assert.equal( order++, 1, "radio focus" ); - } ) - .on( "blur", function() { - assert.equal( order++, 0, "radio blur" ); - } ); + $text + .on( "focus", function() { + assert.equal( order++, 1, "text focus" ); + } ) + .on( "blur", function() { + assert.equal( order++, 0, "text blur" ); + } ); + $radio + .on( "focus", function() { + assert.equal( order++, 1, "radio focus" ); + } ) + .on( "blur", function() { + assert.equal( order++, 0, "radio blur" ); + } ); - // Enabled input getting focus - order = 0; - assert.equal( document.activeElement, $radio[ 0 ], "radio has focus" ); - $text.trigger( "focus" ); - setTimeout( function() { + // Enabled input getting focus + order = 0; + assert.equal( document.activeElement, $radio[ 0 ], "radio has focus" ); + $text.trigger( "focus" ); - // DOM focus is unreliable in TestSwarm - if ( QUnit.isSwarm && order === 0 ) { - assert.ok( true, "GAP: Could not observe focus change" ); - assert.ok( true, "GAP: Could not observe focus change" ); - } + // DOM focus is unreliable in TestSwarm + if ( QUnit.isSwarm && order === 0 ) { + assert.ok( true, "GAP: Could not observe focus change" ); + assert.ok( true, "GAP: Could not observe focus change" ); + } - assert.equal( document.activeElement, $text[ 0 ], "text has focus" ); + assert.equal( document.activeElement, $text[ 0 ], "text has focus" ); - // Run handlers without native method on an input - order = 1; - $radio.triggerHandler( "focus" ); + // Run handlers without native method on an input + order = 1; + $radio.triggerHandler( "focus" ); - // Clean up - $text.off(); - $radio.off(); - done(); - }, 50 ); - }, 50 ); + // Clean up + $text.off(); + $radio.off(); } ); QUnit.test( "Event handling works with multiple async focus events (gh-4350)", function( assert ) { @@ -3199,10 +3216,6 @@ QUnit.test( "Event handling works with multiple async focus events (gh-4350)", f var remaining = 3, input = jQuery( "#name" ), - - // Support: IE <=9 - 11+ - // focus and blur events are asynchronous; this is the resulting mess. - // The browser window must be topmost for this to work properly!! done = assert.async(); input @@ -3212,6 +3225,17 @@ QUnit.test( "Event handling works with multiple async focus events (gh-4350)", f if ( remaining > 0 ) { input.trigger( "blur" ); } else { + + if ( QUnit.isIE ) { + + // Support: <=IE 11+ + // In IE, one of the blurs sometimes triggers a focus on body + // which in turn restores focus to the input, leading to 4 assertions + // firing instead of three. This only happens if other tests are + // running on the same test page. Avoid this issue in tests by removing + // the handler early. + input.off( "focus" ); + } done(); } } ) @@ -3237,6 +3261,45 @@ QUnit.test( "Event handling works with multiple async focus events (gh-4350)", f } ); } ); +// Support: IE <=9 - 11+ +// focus and blur events are asynchronous. +// The browser window must be topmost for this to work properly!! +QUnit.test( "async focus queues properly (gh-4859)", function( assert ) { + assert.expect( 1 ); + + var $text = jQuery( "#text1" ), + $radio = jQuery( "#radio1" ), + done = assert.async(); + + $text.trigger( "focus" ); + $radio.trigger( "focus" ); + $text.trigger( "focus" ); + + setTimeout( function() { + assert.equal( document.activeElement, $text[ 0 ], "focus follows the last trigger" ); + done(); + }, 500 ); +} ); + +// Support: IE <=9 - 11+ +// focus and blur events are asynchronous. +// The browser window must be topmost for this to work properly!! +QUnit.test( "async focus queues properly with blur (gh-4856)", function( assert ) { + assert.expect( 1 ); + + var $text = jQuery( "#text1" ), + done = assert.async(); + + $text.trigger( "focus" ); + $text.trigger( "blur" ); + $text.trigger( "focus" ); + + setTimeout( function() { + assert.equal( document.activeElement, $text[ 0 ], "focus-after-blur is respected" ); + done(); + }, 500 ); +} ); + QUnit.test( "native-backed events preserve trigger data (gh-1741, gh-4139)", function( assert ) { assert.expect( 17 ); @@ -3246,12 +3309,7 @@ QUnit.test( "native-backed events preserve trigger data (gh-1741, gh-4139)", fun targets = jQuery( parent[ 0 ].childNodes ), checkbox = jQuery( targets[ 0 ] ), data = [ "arg1", "arg2" ], - slice = data.slice, - - // Support: IE <=9 - 11+ - // focus and blur events are asynchronous; this is the resulting mess. - // The browser window must be topmost for this to work properly!! - done = assert.async(); + slice = data.slice; // click (gh-4139) assert.strictEqual( targets[ 0 ].checked, false, "checkbox unchecked before click" ); @@ -3277,18 +3335,26 @@ QUnit.test( "native-backed events preserve trigger data (gh-1741, gh-4139)", fun var type = event.type; assert.deepEqual( slice.call( arguments, 1 ), data, type + " handler received correct data" ); + + if ( QUnit.isIE && type === "focus" ) { + + // Support: <=IE 11+ + // In IE, one of the blurs sometimes triggers a focus on body + // which in turn restores focus to the input, leading to 4 assertions + // firing instead of three. This only happens if other tests are + // running on the same test page. Avoid this issue in tests by removing + // the handler early. + checkbox.off( "focus" ); + } } ); checkbox.trigger( "focus", data ); - setTimeout( function() { - assert.strictEqual( document.activeElement, checkbox[ 0 ], - "element focused after focus event (default action)" ); - checkbox.trigger( "blur", data ); - setTimeout( function() { - assert.notEqual( document.activeElement, checkbox[ 0 ], - "element not focused after blur event (default action)" ); - done(); - }, 50 ); - }, 50 ); + + assert.strictEqual( document.activeElement, checkbox[ 0 ], + "element focused after focus event (default action)" ); + checkbox.trigger( "blur", data ); + + assert.notEqual( document.activeElement, checkbox[ 0 ], + "element not focused after blur event (default action)" ); } ); QUnit.test( "focus change during a focus handler (gh-4382)", function( assert ) { @@ -3341,6 +3407,22 @@ QUnit.test( "trigger(focus) works after .on(focus).off(focus) (gh-4867)", functi assert.equal( document.activeElement, input[ 0 ], "input has focus" ); } ); +QUnit.test( "trigger(focus) works after focusing when hidden (gh-4950)", function( assert ) { + assert.expect( 1 ); + + var input = jQuery( "<input />" ); + + input.appendTo( "#qunit-fixture" ); + + input + .css( "display", "none" ) + .trigger( "focus" ) + .css( "display", "" ) + .trigger( "focus" ); + + assert.equal( document.activeElement, input[ 0 ], "input has focus" ); +} ); + // TODO replace with an adaptation of // https://github.com/jquery/jquery/pull/1367/files#diff-a215316abbaabdf71857809e8673ea28R2464 ( function() { |