jquery/test/unit/event.js

3629 lines
104 KiB
JavaScript
Raw Permalink Normal View History

QUnit.module( "event", {
beforeEach: function() {
document.body.focus();
},
afterEach: moduleTeardown
} );
2006-11-18 13:37:01 +00:00
QUnit.test( "null or undefined handler", function( assert ) {
assert.expect( 4 );
// Supports Fixes bug trac-7229
try {
jQuery( "#firstp" ).on( "click", null );
assert.ok( true, "Passing a null handler will not throw an exception" );
} catch ( e ) {}
try {
jQuery( "#firstp" ).on( "click", undefined );
assert.ok( true, "Passing an undefined handler will not throw an exception" );
} catch ( e ) {}
var expectedElem = jQuery( "#firstp" );
var actualElem = expectedElem.on( "click", null );
assert.equal( actualElem, expectedElem, "Passing a null handler should return the original element" );
actualElem = expectedElem.on( "click", undefined );
assert.equal( actualElem, expectedElem, "Passing a null handler should return the original element" );
} );
QUnit.test( "on() with non-null,defined data", function( assert ) {
assert.expect( 2 );
var handler = function( event, data ) {
assert.equal( data, 0, "non-null, defined data (zero) is correctly passed" );
};
jQuery( "#foo" ).on( "foo.on", handler );
jQuery( "div" ).on( "foo.delegate", "#foo", handler );
jQuery( "#foo" ).trigger( "foo", 0 );
jQuery( "#foo" ).off( "foo.on", handler );
jQuery( "div" ).off( "foo.delegate", "#foo" );
} );
QUnit.test( "Handler changes and .trigger() order", function( assert ) {
assert.expect( 1 );
var markup = jQuery(
"<div><div><p><span><b class=\"a\">b</b></span></p></div></div>"
),
path = "";
markup
.find( "*" ).addBack().on( "click", function() {
path += this.nodeName.toLowerCase() + " ";
} )
.filter( "b" ).on( "click", function( e ) {
// Removing span should not stop propagation to original parents
if ( e.target === this ) {
jQuery( this ).parent().remove();
}
} );
markup.find( "b" ).trigger( "click" );
assert.equal( path, "b p div div ", "Delivered all events" );
markup.remove();
} );
QUnit.test( "on(), with data", function( assert ) {
assert.expect( 4 );
var test, handler, handler2;
handler = function( event ) {
assert.ok( event.data, "on() with data, check passed data exists" );
assert.equal( event.data.foo, "bar", "on() with data, Check value of passed data" );
};
jQuery( "#firstp" ).on( "click", { "foo": "bar" }, handler ).trigger( "click" ).off( "click", handler );
assert.ok( !jQuery._data( jQuery( "#firstp" )[ 0 ], "events" ), "Event handler unbound when using data." );
2011-04-17 00:39:30 +00:00
test = function() {};
handler2 = function( event ) {
assert.equal( event.data, test, "on() with function data, Check value of passed data" );
2011-04-17 00:39:30 +00:00
};
jQuery( "#firstp" ).on( "click", test, handler2 ).trigger( "click" ).off( "click", handler2 );
} );
QUnit.test( "click(), with data", function( assert ) {
assert.expect( 3 );
var handler = function( event ) {
assert.ok( event.data, "on() with data, check passed data exists" );
assert.equal( event.data.foo, "bar", "on() with data, Check value of passed data" );
};
jQuery( "#firstp" ).on( "click", { "foo": "bar" }, handler ).trigger( "click" ).off( "click", handler );
assert.ok( !jQuery._data( jQuery( "#firstp" )[ 0 ], "events" ), "Event handler unbound when using data." );
} );
QUnit.test( "on(), with data, trigger with data", function( assert ) {
assert.expect( 4 );
var handler = function( event, data ) {
assert.ok( event.data, "check passed data exists" );
assert.equal( event.data.foo, "bar", "Check value of passed data" );
assert.ok( data, "Check trigger data" );
assert.equal( data.bar, "foo", "Check value of trigger data" );
};
jQuery( "#firstp" ).on( "click", { foo: "bar" }, handler ).trigger( "click", [ { bar: "foo" } ] ).off( "click", handler );
} );
QUnit.test( "on(), multiple events at once", function( assert ) {
assert.expect( 2 );
var handler,
clickCounter = 0,
mouseoverCounter = 0;
handler = function( event ) {
if ( event.type === "click" ) {
clickCounter += 1;
} else if ( event.type === "mouseover" ) {
mouseoverCounter += 1;
2012-06-21 19:30:24 +00:00
}
};
jQuery( "#firstp" ).on( "click mouseover", handler ).trigger( "click" ).trigger( "mouseover" );
assert.equal( clickCounter, 1, "on() with multiple events at once" );
assert.equal( mouseoverCounter, 1, "on() with multiple events at once" );
} );
QUnit.test( "on(), five events at once", function( assert ) {
assert.expect( 1 );
var count = 0,
handler = function() {
count++;
};
jQuery( "#firstp" ).on( "click mouseover foo bar baz", handler )
.trigger( "click" ).trigger( "mouseover" )
.trigger( "foo" ).trigger( "bar" )
.trigger( "baz" );
assert.equal( count, 5, "on() five events at once" );
} );
QUnit.test( "on(), multiple events at once and namespaces", function( assert ) {
assert.expect( 7 );
var cur, div,
obj = {};
div = jQuery( "<div></div>" ).on( "focusin.a", function( e ) {
assert.equal( e.type, cur, "Verify right single event was fired." );
} );
cur = "focusin";
div.trigger( "focusin.a" );
// manually clean up detached elements
div.remove();
div = jQuery( "<div></div>" ).on( "click mouseover", obj, function( e ) {
assert.equal( e.type, cur, "Verify right multi event was fired." );
assert.equal( e.data, obj, "Make sure the data came in correctly." );
} );
cur = "click";
div.trigger( "click" );
cur = "mouseover";
div.trigger( "mouseover" );
// manually clean up detached elements
div.remove();
div = jQuery( "<div></div>" ).on( "focusin.a focusout.b", function( e ) {
assert.equal( e.type, cur, "Verify right multi event was fired." );
} );
cur = "focusin";
div.trigger( "focusin.a" );
cur = "focusout";
div.trigger( "focusout.b" );
// manually clean up detached elements
div.remove();
} );
QUnit.test( "on(), namespace with special add", function( assert ) {
assert.expect( 27 );
var i = 0,
div = jQuery( "<div></div>" ).appendTo( "#qunit-fixture" ).on( "test", function() {
assert.ok( true, "Test event fired." );
} );
jQuery.event.special.test = {
_default: function( e, data ) {
assert.equal( e.type, "test", "Make sure we're dealing with a test event." );
assert.ok( data, "And that trigger data was passed." );
assert.strictEqual( e.target, div[ 0 ], "And that the target is correct." );
assert.equal( this, window, "And that the context is correct." );
},
setup: function() {},
teardown: function() {
assert.ok( true, "Teardown called." );
},
add: function( handleObj ) {
var handler = handleObj.handler;
handleObj.handler = function( e ) {
e.xyz = ++i;
handler.apply( this, arguments );
};
},
remove: function() {
assert.ok( true, "Remove called." );
}
};
div.on( "test.a", { x: 1 }, function( e ) {
assert.ok( !!e.xyz, "Make sure that the data is getting passed through." );
assert.equal( e.data.x, 1, "Make sure data is attached properly." );
} );
div.on( "test.b", { x: 2 }, function( e ) {
assert.ok( !!e.xyz, "Make sure that the data is getting passed through." );
assert.equal( e.data.x, 2, "Make sure data is attached properly." );
} );
// Should trigger 5
div.trigger( "test", 33.33 );
// Should trigger 2
div.trigger( "test.a", "George Harrison" );
// Should trigger 2
div.trigger( "test.b", { year: 1982 } );
// Should trigger 4
div.off( "test" );
div = jQuery( "<div></div>" ).on( "test", function() {
assert.ok( true, "Test event fired." );
} );
// Should trigger 2
div.appendTo( "#qunit-fixture" ).remove();
delete jQuery.event.special.test;
} );
QUnit.test( "on(), no data", function( assert ) {
assert.expect( 1 );
var handler = function( event ) {
assert.ok( !event.data, "Check that no data is added to the event object" );
};
jQuery( "#firstp" ).on( "click", handler ).trigger( "click" );
} );
QUnit.test( "on/one/off(Object)", function( assert ) {
assert.expect( 6 );
var $elem,
clickCounter = 0,
mouseoverCounter = 0;
2012-06-21 19:30:24 +00:00
function handler( event ) {
if ( event.type === "click" ) {
clickCounter++;
} else if ( event.type === "mouseover" ) {
mouseoverCounter++;
2012-06-21 19:30:24 +00:00
}
}
function handlerWithData( event ) {
if ( event.type === "click" ) {
clickCounter += event.data;
} else if ( event.type === "mouseover" ) {
mouseoverCounter += event.data;
2012-06-21 19:30:24 +00:00
}
}
function trigger() {
$elem.trigger( "click" ).trigger( "mouseover" );
}
$elem = jQuery( "#firstp" )
// Regular bind
.on( {
"click": handler,
"mouseover": handler
} )
// Bind with data
.one( {
"click": handlerWithData,
"mouseover": handlerWithData
}, 2 );
trigger();
assert.equal( clickCounter, 3, "on(Object)" );
assert.equal( mouseoverCounter, 3, "on(Object)" );
trigger();
assert.equal( clickCounter, 4, "on(Object)" );
assert.equal( mouseoverCounter, 4, "on(Object)" );
jQuery( "#firstp" ).off( {
"click": handler,
"mouseover": handler
} );
trigger();
assert.equal( clickCounter, 4, "on(Object)" );
assert.equal( mouseoverCounter, 4, "on(Object)" );
} );
QUnit.test( "on/off(Object), on/off(Object, String)", function( assert ) {
assert.expect( 6 );
var events,
clickCounter = 0,
mouseoverCounter = 0,
$p = jQuery( "#firstp" ),
$a = $p.find( "a" ).eq( 0 );
events = {
"click": function( event ) {
clickCounter += ( event.data || 1 );
},
"mouseover": function( event ) {
mouseoverCounter += ( event.data || 1 );
}
};
function trigger() {
$a.trigger( "click" ).trigger( "mouseover" );
}
jQuery( document ).on( events, "#firstp a" );
$p.on( events, "a", 2 );
trigger();
assert.equal( clickCounter, 3, "on" );
assert.equal( mouseoverCounter, 3, "on" );
$p.off( events, "a" );
trigger();
assert.equal( clickCounter, 4, "off" );
assert.equal( mouseoverCounter, 4, "off" );
jQuery( document ).off( events, "#firstp a" );
trigger();
assert.equal( clickCounter, 4, "off" );
assert.equal( mouseoverCounter, 4, "off" );
} );
QUnit.test( "on immediate propagation", function( assert ) {
assert.expect( 2 );
var lastClick,
$p = jQuery( "#firstp" ),
$a = $p.find( "a" ).eq( 0 );
lastClick = "";
jQuery( document ).on( "click", "#firstp a", function( e ) {
lastClick = "click1";
e.stopImmediatePropagation();
} );
jQuery( document ).on( "click", "#firstp a", function() {
lastClick = "click2";
} );
$a.trigger( "click" );
assert.equal( lastClick, "click1", "on stopImmediatePropagation" );
jQuery( document ).off( "click", "#firstp a" );
lastClick = "";
$p.on( "click", "a", function( e ) {
lastClick = "click1";
e.stopImmediatePropagation();
} );
$p.on( "click", "a", function() {
lastClick = "click2";
} );
$a.trigger( "click" );
assert.equal( lastClick, "click1", "on stopImmediatePropagation" );
$p.off( "click", "**" );
} );
QUnit.test( "on bubbling, isDefaultPrevented, stopImmediatePropagation", function( assert ) {
assert.expect( 3 );
var $anchor2 = jQuery( "#anchor2" ),
$main = jQuery( "#qunit-fixture" ),
neverCallMe = function() {
assert.ok( false, "immediate propagation should have been stopped" );
},
fakeClick = function( $jq ) {
// Use a native click so we don't get jQuery simulated bubbling
var e = document.createEvent( "MouseEvents" );
e.initEvent( "click", true, true );
$jq[ 0 ].dispatchEvent( e );
};
$anchor2.on( "click", function( e ) {
e.preventDefault();
} );
$main.on( "click", "#foo", function( e ) {
assert.equal( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" );
} );
fakeClick( $anchor2 );
$anchor2.off( "click" );
$main.off( "click", "**" );
$anchor2.on( "click", function() {
// Let the default action occur
} );
$main.on( "click", "#foo", function( e ) {
assert.equal( e.isDefaultPrevented(), false, "isDefaultPrevented false passed to bubbled event" );
} );
fakeClick( $anchor2 );
$anchor2.off( "click" );
$main.off( "click", "**" );
$anchor2.on( "click", function( e ) {
e.stopImmediatePropagation();
assert.ok( true, "anchor was clicked and prop stopped" );
} );
$anchor2[ 0 ].addEventListener( "click", neverCallMe, false );
fakeClick( $anchor2 );
$anchor2[ 0 ].removeEventListener( "click", neverCallMe );
} );
QUnit.test( "triggered events stopPropagation() for natively-bound events", function( assert ) {
assert.expect( 1 );
var $button = jQuery( "#button" ),
$parent = $button.parent(),
neverCallMe = function() {
assert.ok( false, "propagation should have been stopped" );
},
stopPropagationCallback = function( e ) {
assert.ok( true, "propagation is stopped" );
e.stopPropagation();
};
$parent[ 0 ].addEventListener( "click", neverCallMe );
$button.on( "click", stopPropagationCallback );
$button.trigger( "click" );
$parent[ 0 ].removeEventListener( "click", neverCallMe );
$button.off( "click", stopPropagationCallback );
} );
QUnit.test( "trigger() works with events that were previously stopped", function( assert ) {
assert.expect( 0 );
var $button = jQuery( "#button" ),
$parent = $button.parent(),
neverCallMe = function() {
assert.ok( false, "propagation should have been stopped" );
};
$parent[ 0 ].addEventListener( "click", neverCallMe );
$button.on( "click", neverCallMe );
var clickEvent = jQuery.Event( "click" );
clickEvent.stopPropagation();
$button.trigger( clickEvent );
$parent[ 0 ].removeEventListener( "click", neverCallMe );
$button.off( "click", neverCallMe );
} );
QUnit.test( "on(), iframes", function( assert ) {
assert.expect( 1 );
// events don't work with iframes, see trac-939 - this test fails in IE because of contentDocument
var doc = jQuery( "#loadediframe" ).contents();
jQuery( "div", doc ).on( "click", function() {
assert.ok( true, "Binding to element inside iframe" );
} ).trigger( "click" ).off( "click" );
} );
QUnit.test( "on(), trigger change on select", function( assert ) {
assert.expect( 5 );
var counter = 0;
function selectOnChange( event ) {
assert.equal( event.data, counter++, "Event.data is not a global event object" );
2012-06-21 19:30:24 +00:00
}
jQuery( "#form select" ).each( function( i ) {
jQuery( this ).on( "change", i, selectOnChange );
} ).trigger( "change" );
} );
QUnit.test( "on(), namespaced events, cloned events", function( assert ) {
assert.expect( 18 );
var firstp = jQuery( "#firstp" );
firstp.on( "custom.test", function() {
assert.ok( false, "Custom event triggered" );
} );
firstp.on( "click", function( e ) {
assert.ok( true, "Normal click triggered" );
assert.equal( e.type + e.namespace, "click", "Check that only click events trigger this fn" );
} );
firstp.on( "click.test", function( e ) {
var check = "click";
assert.ok( true, "Namespaced click triggered" );
if ( e.namespace ) {
check += "test";
}
assert.equal( e.type + e.namespace, check, "Check that only click/click.test events trigger this fn" );
} );
//clone(true) element to verify events are cloned correctly
firstp = firstp.add( firstp.clone( true ).attr( "id", "firstp2" ).insertBefore( firstp ) );
// Trigger both bound fn (8)
firstp.trigger( "click" );
// Trigger one bound fn (4)
firstp.trigger( "click.test" );
// Remove only the one fn
firstp.off( "click.test" );
// Trigger the remaining fn (4)
firstp.trigger( "click" );
// Remove the remaining namespaced fn
firstp.off( ".test" );
// Try triggering the custom event (0)
firstp.trigger( "custom" );
// using contents will get comments regular, text, and comment nodes
jQuery( "#nonnodes" ).contents().on( "tester", function() {
assert.equal( this.nodeType, 1, "Check node,textnode,comment on just does real nodes" );
} ).trigger( "tester" );
// Make sure events stick with appendTo'd elements (which are cloned) trac-2027
jQuery( "<a href='#fail' class='test'>test</a>" ).on( "click", function() {
return false;
} ).appendTo( "#qunit-fixture" );
assert.ok( jQuery( "a.test" ).eq( 0 ).triggerHandler( "click" ) === false, "Handler is bound to appendTo'd elements" );
} );
QUnit.test( "on(), multi-namespaced events", function( assert ) {
assert.expect( 6 );
var order = [
"click.test.abc",
"click.test.abc",
"click.test",
"click.test.abc",
"click.test",
"custom.test2"
];
function check( name, msg ) {
assert.deepEqual( name, order.shift(), msg );
}
jQuery( "#firstp" ).on( "custom.test", function() {
check( "custom.test", "Custom event triggered" );
} );
jQuery( "#firstp" ).on( "custom.test2", function() {
check( "custom.test2", "Custom event triggered" );
} );
jQuery( "#firstp" ).on( "click.test", function() {
check( "click.test", "Normal click triggered" );
} );
jQuery( "#firstp" ).on( "click.test.abc", function() {
check( "click.test.abc", "Namespaced click triggered" );
} );
// Those would not trigger/off (trac-5303)
jQuery( "#firstp" ).trigger( "click.a.test" );
jQuery( "#firstp" ).off( "click.a.test" );
// Trigger both bound fn (1)
jQuery( "#firstp" ).trigger( "click.test.abc" );
// Trigger one bound fn (1)
jQuery( "#firstp" ).trigger( "click.abc" );
// Trigger two bound fn (2)
jQuery( "#firstp" ).trigger( "click.test" );
// Remove only the one fn
jQuery( "#firstp" ).off( "click.abc" );
// Trigger the remaining fn (1)
jQuery( "#firstp" ).trigger( "click" );
// Remove the remaining fn
jQuery( "#firstp" ).off( ".test" );
// Trigger the remaining fn (1)
jQuery( "#firstp" ).trigger( "custom" );
} );
QUnit.test( "namespace-only event binding is a no-op", function( assert ) {
assert.expect( 2 );
jQuery( "#firstp" )
.on( ".whoops", function() {
assert.ok( false, "called a namespace-only event" );
} )
.on( "whoops", function() {
assert.ok( true, "called whoops" );
} )
.trigger( "whoops" ) // 1
.off( ".whoops" )
.trigger( "whoops" ) // 2
.off( "whoops" );
} );
QUnit.test( "Empty namespace is ignored", function( assert ) {
assert.expect( 1 );
jQuery( "#firstp" )
.on( "meow.", function( e ) {
assert.equal( e.namespace, "", "triggered a namespace-less meow event" );
} )
.trigger( "meow." )
.off( "meow." );
} );
QUnit.test( "on(), with same function", function( assert ) {
assert.expect( 2 );
var count = 0, func = function() {
count++;
};
jQuery( "#liveHandlerOrder" ).on( "foo.bar", func ).on( "foo.zar", func );
jQuery( "#liveHandlerOrder" ).trigger( "foo.bar" );
assert.equal( count, 1, "Verify binding function with multiple namespaces." );
jQuery( "#liveHandlerOrder" ).off( "foo.bar", func ).off( "foo.zar", func );
jQuery( "#liveHandlerOrder" ).trigger( "foo.bar" );
assert.equal( count, 1, "Verify that removing events still work." );
} );
QUnit.test( "on(), make sure order is maintained", function( assert ) {
assert.expect( 1 );
var elem = jQuery( "#firstp" ), log = [], check = [];
jQuery.each( new Array( 100 ), function( i ) {
elem.on( "click", function() {
log.push( i );
} );
check.push( i );
2012-06-21 19:30:24 +00:00
} );
elem.trigger( "click" );
assert.equal( log.join( "," ), check.join( "," ), "Make sure order was maintained." );
elem.off( "click" );
} );
QUnit.test( "on(), with different this object", function( assert ) {
assert.expect( 4 );
var thisObject = { myThis: true },
data = { myData: true },
handler1 = function() {
assert.equal( this, thisObject, "on() with different this object" );
}.bind( thisObject ),
handler2 = function( event ) {
assert.equal( this, thisObject, "on() with different this object and data" );
assert.equal( event.data, data, "on() with different this object and data" );
}.bind( thisObject );
jQuery( "#firstp" )
.on( "click", handler1 ).trigger( "click" ).off( "click", handler1 )
.on( "click", data, handler2 ).trigger( "click" ).off( "click", handler2 );
assert.ok( !jQuery._data( jQuery( "#firstp" )[ 0 ], "events" ), "Event handler unbound when using different this object and data." );
} );
QUnit.test( "on(name, false), off(name, false)", function( assert ) {
assert.expect( 3 );
var main = 0;
jQuery( "#qunit-fixture" ).on( "click", function() {
main++;
} );
jQuery( "#ap" ).trigger( "click" );
assert.equal( main, 1, "Verify that the trigger happened correctly." );
main = 0;
jQuery( "#ap" ).on( "click", false );
jQuery( "#ap" ).trigger( "click" );
assert.equal( main, 0, "Verify that no bubble happened." );
main = 0;
jQuery( "#ap" ).off( "click", false );
jQuery( "#ap" ).trigger( "click" );
assert.equal( main, 1, "Verify that the trigger happened correctly." );
// manually clean up events from elements outside the fixture
jQuery( "#qunit-fixture" ).off( "click" );
} );
QUnit.test( "on(name, selector, false), off(name, selector, false)", function( assert ) {
assert.expect( 3 );
var main = 0;
jQuery( "#qunit-fixture" ).on( "click", "#ap", function() {
main++;
} );
jQuery( "#ap" ).trigger( "click" );
assert.equal( main, 1, "Verify that the trigger happened correctly." );
main = 0;
jQuery( "#ap" ).on( "click", "#groups", false );
jQuery( "#groups" ).trigger( "click" );
assert.equal( main, 0, "Verify that no bubble happened." );
main = 0;
jQuery( "#ap" ).off( "click", "#groups", false );
jQuery( "#groups" ).trigger( "click" );
assert.equal( main, 1, "Verify that the trigger happened correctly." );
jQuery( "#qunit-fixture" ).off( "click", "#ap" );
} );
QUnit.test( "on()/trigger()/off() on plain object", function( assert ) {
assert.expect( 7 );
var events,
obj = {};
// Make sure it doesn't complain when no events are found
jQuery( obj ).trigger( "test" );
// Make sure it doesn't complain when no events are found
jQuery( obj ).off( "test" );
jQuery( obj ).on( {
"test": function() {
assert.ok( true, "Custom event run." );
},
"submit": function() {
assert.ok( true, "Custom submit event run." );
}
} );
events = jQuery._data( obj, "events" );
assert.ok( events, "Object has events bound." );
assert.equal( obj.events, undefined, "Events object on plain objects is not events" );
assert.equal( obj.test, undefined, "Make sure that test event is not on the plain object." );
assert.equal( obj.handle, undefined, "Make sure that the event handler is not on the plain object." );
// Should trigger 1
jQuery( obj ).trigger( "test" );
jQuery( obj ).trigger( "submit" );
jQuery( obj ).off( "test" );
jQuery( obj ).off( "submit" );
// Should trigger 0
jQuery( obj ).trigger( "test" );
// Make sure it doesn't complain when no events are found
jQuery( obj ).off( "test" );
assert.equal( obj && obj[ jQuery.expando ] &&
obj[ jQuery.expando ][ jQuery.expando ] &&
obj[ jQuery.expando ][ jQuery.expando ].events, undefined, "Make sure events object is removed" );
} );
QUnit.test( "off(type)", function( assert ) {
assert.expect( 1 );
var message, func,
$elem = jQuery( "#firstp" );
function error() {
assert.ok( false, message );
}
message = "unbind passing function";
$elem.on( "error1", error ).off( "error1", error ).triggerHandler( "error1" );
message = "unbind all from event";
$elem.on( "error1", error ).off( "error1" ).triggerHandler( "error1" );
message = "unbind all";
$elem.on( "error1", error ).off().triggerHandler( "error1" );
message = "unbind many with function";
$elem.on( "error1 error2", error )
.off( "error1 error2", error )
.trigger( "error1" ).triggerHandler( "error2" );
message = "unbind many"; // trac-3538
$elem.on( "error1 error2", error )
.off( "error1 error2" )
.trigger( "error1" ).triggerHandler( "error2" );
message = "unbind without a type or handler";
$elem.on( "error1 error2.test", error )
.off()
.trigger( "error1" ).triggerHandler( "error2" );
// Should only unbind the specified function
jQuery( document ).on( "click", function() {
assert.ok( true, "called handler after selective removal" );
} );
func = function() {};
jQuery( document )
.on( "click", func )
.off( "click", func )
.trigger( "click" )
.off( "click" );
} );
QUnit.test( "off(eventObject)", function( assert ) {
assert.expect( 4 );
var $elem = jQuery( "#firstp" ),
num;
function check( expected ) {
num = 0;
$elem.trigger( "foo" ).triggerHandler( "bar" );
assert.equal( num, expected, "Check the right handlers are triggered" );
}
$elem
// This handler shouldn't be unbound
.on( "foo", function() {
num += 1;
} )
.on( "foo", function( e ) {
$elem.off( e );
num += 2;
} )
// Neither this one
.on( "bar", function() {
num += 4;
} );
check( 7 );
check( 5 );
$elem.off( "bar" );
check( 1 );
$elem.off();
check( 0 );
} );
QUnit.test( "mouseover triggers mouseenter", function( assert ) {
assert.expect( 1 );
var count = 0,
elem = jQuery( "<a></a>" );
elem.on( "mouseenter", function() {
2012-06-21 19:30:24 +00:00
count++;
} );
elem.trigger( "mouseover" );
assert.equal( count, 1, "make sure mouseover triggers a mouseenter" );
elem.remove();
} );
QUnit.test( "pointerover triggers pointerenter", function( assert ) {
assert.expect( 1 );
var count = 0,
elem = jQuery( "<a></a>" );
elem.on( "pointerenter", function() {
count++;
} );
elem.trigger( "pointerover" );
assert.equal( count, 1, "make sure pointerover triggers a pointerenter" );
elem.remove();
} );
QUnit.test( "withinElement implemented with jQuery.contains()", function( assert ) {
assert.expect( 1 );
jQuery( "#qunit-fixture" ).append( "<div id='jc-outer'><div id='jc-inner'></div></div>" );
jQuery( "#jc-outer" ).on( "mouseenter mouseleave", function( event ) {
assert.equal( this.id, "jc-outer", this.id + " " + event.type );
} );
jQuery( "#jc-inner" ).trigger( "mouseenter" );
} );
QUnit.test( "mouseenter, mouseleave don't catch exceptions", function( assert ) {
assert.expect( 2 );
var elem = jQuery( "#firstp" ).on( "mouseenter mouseleave", function() {
throw "an Exception";
} );
try {
elem.trigger( "mouseenter" );
} catch ( e ) {
assert.equal( e, "an Exception", "mouseenter doesn't catch exceptions" );
}
try {
elem.trigger( "mouseleave" );
} catch ( e ) {
assert.equal( e, "an Exception", "mouseleave doesn't catch exceptions" );
}
} );
QUnit.test( "trigger() bubbling", function( assert ) {
assert.expect( 18 );
var win = 0, doc = 0, html = 0, body = 0, main = 0, ap = 0;
jQuery( window ).on( "click", function() {
win++;
} );
jQuery( document ).on( "click", function( e ) {
if ( e.target !== document ) {
doc++;
}
} );
jQuery( "html" ).on( "click", function() {
html++;
} );
jQuery( "body" ).on( "click", function() {
body++;
} );
jQuery( "#qunit-fixture" ).on( "click", function() {
main++;
} );
jQuery( "#ap" ).on( "click", function() {
ap++; return false;
} );
jQuery( "html" ).trigger( "click" );
assert.equal( win, 1, "HTML bubble" );
assert.equal( doc, 1, "HTML bubble" );
assert.equal( html, 1, "HTML bubble" );
jQuery( "body" ).trigger( "click" );
assert.equal( win, 2, "Body bubble" );
assert.equal( doc, 2, "Body bubble" );
assert.equal( html, 2, "Body bubble" );
assert.equal( body, 1, "Body bubble" );
jQuery( "#qunit-fixture" ).trigger( "click" );
assert.equal( win, 3, "Main bubble" );
assert.equal( doc, 3, "Main bubble" );
assert.equal( html, 3, "Main bubble" );
assert.equal( body, 2, "Main bubble" );
assert.equal( main, 1, "Main bubble" );
jQuery( "#ap" ).trigger( "click" );
assert.equal( doc, 3, "ap bubble" );
assert.equal( html, 3, "ap bubble" );
assert.equal( body, 2, "ap bubble" );
assert.equal( main, 1, "ap bubble" );
assert.equal( ap, 1, "ap bubble" );
jQuery( document ).trigger( "click" );
assert.equal( win, 4, "doc bubble" );
2012-06-21 19:30:24 +00:00
// manually clean up events from elements outside the fixture
jQuery( window ).off( "click" );
jQuery( document ).off( "click" );
jQuery( "html, body, #qunit-fixture" ).off( "click" );
} );
QUnit.test( "trigger(type, [data], [fn])", function( assert ) {
assert.expect( 16 );
var $elem, pass, form, elem2,
handler = function( event, a, b, c ) {
assert.equal( event.type, "click", "check passed data" );
assert.equal( a, 1, "check passed data" );
assert.equal( b, "2", "check passed data" );
assert.equal( c, "abc", "check passed data" );
return "test";
};
$elem = jQuery( "#firstp" );
// Simulate a "native" click
$elem[ 0 ].click = function() {
assert.ok( true, "Native call was triggered" );
};
jQuery( document ).on( "mouseenter", "#firstp", function() {
assert.ok( true, "Trigger mouseenter bound by on" );
} );
jQuery( document ).on( "mouseleave", "#firstp", function() {
assert.ok( true, "Trigger mouseleave bound by on" );
} );
$elem.trigger( "mouseenter" );
$elem.trigger( "mouseleave" );
jQuery( document ).off( "mouseenter mouseleave", "#firstp" );
2013-02-27 20:44:34 +00:00
// Triggers handlers and native
// Trigger 5
$elem.on( "click", handler ).trigger( "click", [ 1, "2", "abc" ] );
// Simulate a "native" click
$elem[ 0 ].click = function() {
assert.ok( false, "Native call was triggered" );
};
// Trigger only the handlers (no native)
// Triggers 5
assert.equal( $elem.triggerHandler( "click", [ 1, "2", "abc" ] ), "test", "Verify handler response" );
pass = true;
try {
elem2 = jQuery( "#form input" ).eq( 0 );
elem2.get( 0 ).style.display = "none";
elem2.trigger( "focus" );
} catch ( e ) {
pass = false;
}
assert.ok( pass, "Trigger focus on hidden element" );
pass = true;
try {
jQuery( "#qunit-fixture table" ).eq( 0 ).on( "test:test", function() {} ).trigger( "test:test" );
} catch ( e ) {
pass = false;
}
assert.ok( pass, "Trigger on a table with a colon in the even type, see trac-3533" );
form = jQuery( "<form action=''></form>" ).appendTo( "body" );
// Make sure it can be prevented locally
form.on( "submit", function() {
assert.ok( true, "Local `on` still works." );
return false;
} );
// Trigger 1
form.trigger( "submit" );
form.off( "submit" );
jQuery( document ).on( "submit", function() {
assert.ok( true, "Make sure bubble works up to document." );
return false;
} );
// Trigger 1
form.trigger( "submit" );
jQuery( document ).off( "submit" );
form.remove();
} );
QUnit.test( "submit event bubbles on copied forms (trac-11649)", function( assert ) {
assert.expect( 3 );
2012-06-21 19:30:24 +00:00
var $formByClone, $formByHTML,
$testForm = jQuery( "#testForm" ),
$fixture = jQuery( "#qunit-fixture" ),
$wrapperDiv = jQuery( "<div></div>" ).appendTo( $fixture );
2012-06-21 19:30:24 +00:00
function noSubmit( e ) {
e.preventDefault();
}
function delegatedSubmit() {
assert.ok( true, "Make sure submit event bubbles up." );
return false;
}
2012-06-21 19:30:24 +00:00
// Attach a delegated submit handler to the parent element
$fixture.on( "submit", "form", delegatedSubmit );
2012-06-21 19:30:24 +00:00
// Trigger form submission to introduce the _submit_attached property
$testForm.on( "submit", noSubmit ).find( "input[name=sub1]" ).trigger( "click" );
2012-06-21 19:30:24 +00:00
// Copy the form via .clone() and .html()
$formByClone = $testForm.clone( true, true ).removeAttr( "id" );
$formByHTML = jQuery( jQuery.parseHTML( $fixture.html() ) ).filter( "#testForm" ).removeAttr( "id" );
$wrapperDiv.append( $formByClone, $formByHTML );
2012-06-21 19:30:24 +00:00
// Check submit bubbling on the copied forms
$wrapperDiv.find( "form" ).on( "submit", noSubmit ).find( "input[name=sub1]" ).trigger( "click" );
2012-06-21 19:30:24 +00:00
// Clean up
$wrapperDiv.remove();
$fixture.off( "submit", "form", delegatedSubmit );
$testForm.off( "submit", noSubmit );
} );
QUnit.test( "change event bubbles on copied forms (trac-11796)", function( assert ) {
assert.expect( 3 );
2012-06-21 19:30:24 +00:00
var $formByClone, $formByHTML,
$form = jQuery( "#form" ),
$fixture = jQuery( "#qunit-fixture" ),
$wrapperDiv = jQuery( "<div></div>" ).appendTo( $fixture );
2012-06-21 19:30:24 +00:00
function delegatedChange() {
assert.ok( true, "Make sure change event bubbles up." );
return false;
}
2012-06-21 19:30:24 +00:00
// Attach a delegated change handler to the form
$fixture.on( "change", "form", delegatedChange );
2012-06-21 19:30:24 +00:00
// Trigger change event to introduce the _change_attached property
$form.find( "select[name=select1]" ).val( "1" ).trigger( "change" );
2012-06-21 19:30:24 +00:00
// Copy the form via .clone() and .html()
$formByClone = $form.clone( true, true ).removeAttr( "id" );
$formByHTML = jQuery( jQuery.parseHTML( $fixture.html() ) ).filter( "#form" ).removeAttr( "id" );
$wrapperDiv.append( $formByClone, $formByHTML );
2012-06-21 19:30:24 +00:00
// Check change bubbling on the copied forms
$wrapperDiv.find( "form select[name=select1]" ).val( "2" ).trigger( "change" );
2012-06-21 19:30:24 +00:00
// Clean up
$wrapperDiv.remove();
$fixture.off( "change", "form", delegatedChange );
} );
QUnit.test( "trigger(eventObject, [data], [fn])", function( assert ) {
assert.expect( 28 );
var event,
$parent = jQuery( "<div id='par'></div>" ).appendTo( "body" ),
$child = jQuery( "<p id='child'>foo</p>" ).appendTo( $parent );
$parent.get( 0 ).style.display = "none";
event = jQuery.Event( "noNew" );
assert.ok( event !== window, "Instantiate jQuery.Event without the 'new' keyword" );
assert.equal( event.type, "noNew", "Verify its type" );
assert.equal( event.isDefaultPrevented(), false, "Verify isDefaultPrevented" );
assert.equal( event.isPropagationStopped(), false, "Verify isPropagationStopped" );
assert.equal( event.isImmediatePropagationStopped(), false, "Verify isImmediatePropagationStopped" );
event.preventDefault();
assert.equal( event.isDefaultPrevented(), true, "Verify isDefaultPrevented" );
event.stopPropagation();
assert.equal( event.isPropagationStopped(), true, "Verify isPropagationStopped" );
event.isPropagationStopped = function() {
return false;
};
event.stopImmediatePropagation();
assert.equal( event.isPropagationStopped(), true, "Verify isPropagationStopped" );
assert.equal( event.isImmediatePropagationStopped(), true, "Verify isPropagationStopped" );
$parent.on( "foo", function( e ) {
// Tries bubbling
assert.equal( e.type, "foo", "Verify event type when passed passing an event object" );
assert.equal( e.target.id, "child", "Verify event.target when passed passing an event object" );
assert.equal( e.currentTarget.id, "par", "Verify event.currentTarget when passed passing an event object" );
assert.equal( e.secret, "boo!", "Verify event object's custom attribute when passed passing an event object" );
} );
// test with an event object
event = new jQuery.Event( "foo" );
2011-04-12 08:47:46 +00:00
event.secret = "boo!";
$child.trigger( event );
// test with a literal object
$child.trigger( { "type": "foo", "secret": "boo!" } );
$parent.off();
function error() {
assert.ok( false, "This assertion shouldn't be reached" );
}
$parent.on( "foo", error );
$child.on( "foo", function( e, a, b, c ) {
assert.equal( arguments.length, 4, "Check arguments length" );
assert.equal( a, 1, "Check first custom argument" );
assert.equal( b, 2, "Check second custom argument" );
assert.equal( c, 3, "Check third custom argument" );
assert.equal( e.isDefaultPrevented(), false, "Verify isDefaultPrevented" );
assert.equal( e.isPropagationStopped(), false, "Verify isPropagationStopped" );
assert.equal( e.isImmediatePropagationStopped(), false, "Verify isImmediatePropagationStopped" );
// Skips both errors
e.stopImmediatePropagation();
return "result";
} );
2009-01-05 23:06:57 +00:00
// We should add this back in when we want to test the order
// in which event handlers are iterated.
//$child.on("foo", error );
event = new jQuery.Event( "foo" );
$child.trigger( event, [ 1, 2, 3 ] ).off();
assert.equal( event.result, "result", "Check event.result attribute" );
// Will error if it bubbles
$child.triggerHandler( "foo" );
$child.off();
$parent.off().remove();
// Ensure triggerHandler doesn't molest its event object (#xxx)
2012-06-21 19:30:24 +00:00
event = jQuery.Event( "zowie" );
jQuery( document ).triggerHandler( event );
assert.equal( event.type, "zowie", "Verify its type" );
assert.equal( event.isPropagationStopped(), false, "propagation not stopped" );
assert.equal( event.isDefaultPrevented(), false, "default not prevented" );
} );
QUnit.test( ".trigger() bubbling on disconnected elements (trac-10489)", function( assert ) {
assert.expect( 2 );
jQuery( window ).on( "click", function() {
assert.ok( false, "click fired on window" );
} );
jQuery( "<div><p>hi</p></div>" )
.on( "click", function() {
assert.ok( true, "click fired on div" );
} )
.find( "p" )
.on( "click", function() {
assert.ok( true, "click fired on p" );
} )
.trigger( "click" )
.off( "click" )
.end()
.off( "click" )
.remove();
jQuery( window ).off( "click" );
} );
QUnit.test( ".trigger() doesn't bubble load event (trac-10717)", function( assert ) {
assert.expect( 1 );
jQuery( window ).on( "load", function() {
assert.ok( false, "load fired on window" );
} );
jQuery( "<img src='" + baseURL + "1x1.jpg' />" )
.appendTo( "body" )
.on( "load", function() {
assert.ok( true, "load fired on img" );
} )
.trigger( "load" )
.remove();
jQuery( window ).off( "load" );
} );
QUnit.test( "Delegated events in SVG (trac-10791; trac-13180)", function( assert ) {
assert.expect( 2 );
var useElem, e,
svg = jQuery(
"<svg height='1' version='1.1' width='1' xmlns='http://www.w3.org/2000/svg'>" +
"<defs><rect id='ref' x='10' y='20' width='100' height='60' r='10' rx='10' ry='10'></rect></defs>" +
"<rect class='svg-by-class' x='10' y='20' width='100' height='60' r='10' rx='10' ry='10'></rect>" +
"<rect id='svg-by-id' x='10' y='20' width='100' height='60' r='10' rx='10' ry='10'></rect>" +
"<use id='use' xlink:href='#ref'></use>" +
"</svg>"
);
jQuery( "#qunit-fixture" )
.append( svg )
.on( "click", "#svg-by-id", function() {
assert.ok( true, "delegated id selector" );
} )
.on( "click", "[class~='svg-by-class']", function() {
assert.ok( true, "delegated class selector" );
} )
.find( "#svg-by-id, [class~='svg-by-class']" )
.trigger( "click" )
.end();
// Fire a native click on an SVGElementInstance (the instance tree of an SVG <use>)
// to confirm that it doesn't break our event delegation handling (trac-13180)
useElem = svg.find( "#use" )[ 0 ];
if ( document.createEvent && useElem && useElem.instanceRoot ) {
e = document.createEvent( "MouseEvents" );
e.initEvent( "click", true, true );
useElem.instanceRoot.dispatchEvent( e );
}
jQuery( "#qunit-fixture" ).off( "click" );
} );
QUnit.test( "Delegated events with malformed selectors (gh-3071)", function( assert ) {
assert.expect( 3 );
assert.throws( function() {
jQuery( "#foo" ).on( "click", ":not", function() {} );
}, "malformed selector throws on attach" );
assert.throws( function() {
jQuery( "#foo" ).on( "click", "nonexistent:not", function() {} );
}, "short-circuitable malformed selector throws on attach" );
jQuery( "#foo > :first-child" ).trigger( "click" );
assert.ok( true, "malformed selector does not throw on event" );
} );
QUnit.test( "Delegated events in forms (trac-10844; trac-11145; trac-8165; trac-11382, trac-11764)", function( assert ) {
assert.expect( 5 );
// Alias names like "id" cause havoc
var form = jQuery(
"<form id='myform'>" +
"<input type='text' name='id' value='secret agent man' />" +
"</form>"
)
.on( "submit", function( event ) {
event.preventDefault();
} )
.appendTo( "body" );
jQuery( "body" )
.on( "submit", "#myform", function() {
assert.ok( true, "delegated id selector with aliased id" );
} )
.find( "#myform" )
.trigger( "submit" )
.end()
.off( "submit" );
form.append( "<input type='text' name='disabled' value='differently abled' />" );
jQuery( "body" )
.on( "submit", "#myform", function() {
assert.ok( true, "delegated id selector with aliased disabled" );
} )
.find( "#myform" )
.trigger( "submit" )
.end()
.off( "submit" );
form
.append( "<button id='nestyDisabledBtn'><span>Zing</span></button>" )
.on( "click", "#nestyDisabledBtn", function() {
assert.ok( true, "click on enabled/disabled button with nesty elements" );
} )
.on( "mouseover", "#nestyDisabledBtn", function() {
assert.ok( true, "mouse on enabled/disabled button with nesty elements" );
} )
.find( "span" )
.trigger( "click" ) // yep
.trigger( "mouseover" ) // yep
.end()
.find( "#nestyDisabledBtn" ).prop( "disabled", true ).end()
.find( "span" )
.trigger( "click" ) // nope
.trigger( "mouseover" ) // yep
.end()
.off( "click" );
form.remove();
} );
QUnit.test( "Submit event can be stopped (trac-11049)", function( assert ) {
assert.expect( 1 );
// Since we manually bubble in IE, make sure inner handlers get a chance to cancel
var form = jQuery(
"<form id='myform'>" +
"<input type='text' name='sue' value='bawls' />" +
"<input type='submit' />" +
"</form>"
)
.appendTo( "body" );
jQuery( "body" )
.on( "submit", function() {
assert.ok( true, "submit bubbled on first handler" );
return false;
} )
.find( "#myform input[type=submit]" )
.each( function() {
this.click();
} )
.end()
.on( "submit", function() {
assert.ok( false, "submit bubbled on second handler" );
return false;
} )
.find( "#myform input[type=submit]" )
.each( function() {
jQuery( this.form ).on( "submit", function( e ) {
e.preventDefault();
e.stopPropagation();
} );
this.click();
} )
.end()
.off( "submit" );
form.remove();
} );
// Support: iOS <=7 - 12+
// iOS has the window.onbeforeunload field but doesn't support the beforeunload
// handler making it impossible to feature-detect the support.
QUnit[ /(ipad|iphone|ipod)/i.test( navigator.userAgent ) ? "skip" : "test" ](
"on(beforeunload)", function( assert ) {
assert.expect( 1 );
var iframe = jQuery( jQuery.parseHTML( "<iframe src='" + baseURL + "event/onbeforeunload.html'><iframe>" ) );
var done = assert.async();
window.onmessage = function( event ) {
try {
var payload = JSON.parse( event.data );
// Ignore unrelated messages
if ( payload.source === "jQuery onbeforeunload iframe test" ) {
assert.ok( payload.event, "beforeunload", "beforeunload event" );
iframe.remove();
window.onmessage = null;
done();
}
} catch ( e ) {
// Messages may come from other sources, like browser extensions;
// some may not be valid JSONs and thus cannot be `JSON.parse`d.
}
};
iframe.appendTo( "#qunit-fixture" );
} );
QUnit.test( "jQuery.Event( type, props )", function( assert ) {
assert.expect( 6 );
var event = jQuery.Event( "keydown", { keyCode: 64 } ),
handler = function( event ) {
assert.ok( "keyCode" in event, "Special property 'keyCode' exists" );
assert.equal( event.keyCode, 64, "event.keyCode has explicit value '64'" );
};
// Supports jQuery.Event implementation
assert.equal( event.type, "keydown", "Verify type" );
// ensure "type" in props won't clobber the one set by constructor
assert.equal( jQuery.inArray( "type", jQuery.event.props ), -1, "'type' property not in props (trac-10375)" );
assert.ok( "keyCode" in event, "Special 'keyCode' property exists" );
assert.strictEqual( jQuery.isPlainObject( event ), false, "Instances of $.Event should not be identified as a plain object." );
jQuery( "body" ).on( "keydown", handler ).trigger( event );
jQuery( "body" ).off( "keydown" );
} );
QUnit.test( "jQuery.Event properties", function( assert ) {
assert.expect( 12 );
var handler,
$structure = jQuery( "<div id='ancestor'><p id='delegate'><span id='target'>shiny</span></p></div>" ),
$target = $structure.find( "#target" );
handler = function( e ) {
assert.strictEqual( e.currentTarget, this, "currentTarget at " + this.id );
assert.equal( e.isTrigger, 3, "trigger at " + this.id );
};
$structure.one( "click", handler );
$structure.one( "click", "p", handler );
$target.one( "click", handler );
$target[ 0 ].onclick = function( e ) {
assert.strictEqual( e.currentTarget, this, "currentTarget at target (native handler)" );
assert.equal( e.isTrigger, 3, "trigger at target (native handler)" );
};
$target.trigger( "click" );
$target.one( "click", function( e ) {
assert.equal( e.isTrigger, 2, "triggerHandler at target" );
} );
$target[ 0 ].onclick = function( e ) {
assert.equal( e.isTrigger, 2, "triggerHandler at target (native handler)" );
};
$target.triggerHandler( "click" );
handler = function( e ) {
assert.strictEqual( e.isTrigger, undefined, "native event at " + this.id );
};
$target.one( "click", handler );
$target[ 0 ].onclick = function( e ) {
assert.strictEqual( e.isTrigger, undefined, "native event at target (native handler)" );
};
fireNative( $target[ 0 ], "click" );
} );
QUnit.test( ".on()/.off()", function( assert ) {
assert.expect( 65 );
var event, clicked, hash, called, livec, lived, livee,
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery( "#body" ).on( "submit", "#qunit-fixture div", function() {
submit++; return false;
} );
jQuery( "#body" ).on( "click", "#qunit-fixture div", function() {
div++;
} );
jQuery( "#body" ).on( "click", "div#nothiddendiv", function() {
livea++;
} );
jQuery( "#body" ).on( "click", "div#nothiddendivchild", function() {
liveb++;
} );
// Nothing should trigger on the body
jQuery( "body" ).trigger( "click" );
assert.equal( submit, 0, "Click on body" );
assert.equal( div, 0, "Click on body" );
assert.equal( livea, 0, "Click on body" );
assert.equal( liveb, 0, "Click on body" );
// This should trigger two events
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "div#nothiddendiv" ).trigger( "click" );
assert.equal( submit, 0, "Click on div" );
assert.equal( div, 1, "Click on div" );
assert.equal( livea, 1, "Click on div" );
assert.equal( liveb, 0, "Click on div" );
// This should trigger three events (w/ bubbling)
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "div#nothiddendivchild" ).trigger( "click" );
assert.equal( submit, 0, "Click on inner div" );
assert.equal( div, 2, "Click on inner div" );
assert.equal( livea, 1, "Click on inner div" );
assert.equal( liveb, 1, "Click on inner div" );
// This should trigger one submit
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "div#nothiddendivchild" ).trigger( "submit" );
assert.equal( submit, 1, "Submit on div" );
assert.equal( div, 0, "Submit on div" );
assert.equal( livea, 0, "Submit on div" );
assert.equal( liveb, 0, "Submit on div" );
// Make sure no other events were removed in the process
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "div#nothiddendivchild" ).trigger( "click" );
assert.equal( submit, 0, "off Click on inner div" );
assert.equal( div, 2, "off Click on inner div" );
assert.equal( livea, 1, "off Click on inner div" );
assert.equal( liveb, 1, "off Click on inner div" );
// Now make sure that the removal works
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "#body" ).off( "click", "div#nothiddendivchild" );
jQuery( "div#nothiddendivchild" ).trigger( "click" );
assert.equal( submit, 0, "off Click on inner div" );
assert.equal( div, 2, "off Click on inner div" );
assert.equal( livea, 1, "off Click on inner div" );
assert.equal( liveb, 0, "off Click on inner div" );
// Make sure that the click wasn't removed too early
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "div#nothiddendiv" ).trigger( "click" );
assert.equal( submit, 0, "off Click on inner div" );
assert.equal( div, 1, "off Click on inner div" );
assert.equal( livea, 1, "off Click on inner div" );
assert.equal( liveb, 0, "off Click on inner div" );
2013-02-27 20:44:34 +00:00
// Make sure that stopPropagation doesn't stop live events
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
jQuery( "#body" ).on( "click", "div#nothiddendivchild", function( e ) {
liveb++; e.stopPropagation();
} );
jQuery( "div#nothiddendivchild" ).trigger( "click" );
assert.equal( submit, 0, "stopPropagation Click on inner div" );
assert.equal( div, 1, "stopPropagation Click on inner div" );
assert.equal( livea, 0, "stopPropagation Click on inner div" );
assert.equal( liveb, 1, "stopPropagation Click on inner div" );
// Make sure click events only fire with primary click
2012-06-21 19:30:24 +00:00
submit = 0; div = 0; livea = 0; liveb = 0;
event = jQuery.Event( "click" );
event.button = 1;
jQuery( "div#nothiddendiv" ).trigger( event );
assert.equal( livea, 0, "on secondary click" );
jQuery( "#body" ).off( "click", "div#nothiddendivchild" );
jQuery( "#body" ).off( "click", "div#nothiddendiv" );
jQuery( "#body" ).off( "click", "#qunit-fixture div" );
jQuery( "#body" ).off( "submit", "#qunit-fixture div" );
// Test binding with a different context
clicked = 0;
jQuery( "#qunit-fixture" ).on( "click", "#foo", function() {
clicked++;
} );
jQuery( "#qunit-fixture div" ).trigger( "click" );
jQuery( "#foo" ).trigger( "click" );
jQuery( "#qunit-fixture" ).trigger( "click" );
jQuery( "body" ).trigger( "click" );
assert.equal( clicked, 2, "on with a context" );
// Test unbinding with a different context
jQuery( "#qunit-fixture" ).off( "click", "#foo" );
jQuery( "#foo" ).trigger( "click" );
assert.equal( clicked, 2, "off with a context" );
// Test binding with event data
jQuery( "#body" ).on( "click", "#foo", true, function( e ) {
assert.equal( e.data, true, "on with event data" );
} );
jQuery( "#foo" ).trigger( "click" );
jQuery( "#body" ).off( "click", "#foo" );
// Test binding with trigger data
jQuery( "#body" ).on( "click", "#foo", function( e, data ) {
assert.equal( data, true, "on with trigger data" );
} );
jQuery( "#foo" ).trigger( "click", true );
jQuery( "#body" ).off( "click", "#foo" );
// Test binding with different this object
jQuery( "#body" ).on( "click", "#foo", function() {
assert.equal( this.foo, "bar", "on with event scope" );
}.bind( { "foo": "bar" } ) );
jQuery( "#foo" ).trigger( "click" );
jQuery( "#body" ).off( "click", "#foo" );
// Test binding with different this object, event data, and trigger data
jQuery( "#body" ).on( "click", "#foo", true, function( e, data ) {
assert.equal( e.data, true, "on with with different this object, event data, and trigger data" );
assert.equal( this.foo, "bar", "on with with different this object, event data, and trigger data" );
assert.equal( data, true, "on with with different this object, event data, and trigger data" );
}.bind( { "foo": "bar" } ) );
jQuery( "#foo" ).trigger( "click", true );
jQuery( "#body" ).off( "click", "#foo" );
// Verify that return false prevents default action
jQuery( "#body" ).on( "click", "#anchor2", function() {
return false;
} );
hash = window.location.hash;
jQuery( "#anchor2" ).trigger( "click" );
assert.equal( window.location.hash, hash, "return false worked" );
jQuery( "#body" ).off( "click", "#anchor2" );
// Verify that .preventDefault() prevents default action
jQuery( "#body" ).on( "click", "#anchor2", function( e ) {
e.preventDefault();
} );
2012-06-21 19:30:24 +00:00
hash = window.location.hash;
jQuery( "#anchor2" ).trigger( "click" );
assert.equal( window.location.hash, hash, "e.preventDefault() worked" );
jQuery( "#body" ).off( "click", "#anchor2" );
// Test binding the same handler to multiple points
called = 0;
function callback() {
called++; return false;
}
jQuery( "#body" ).on( "click", "#nothiddendiv", callback );
jQuery( "#body" ).on( "click", "#anchor2", callback );
jQuery( "#nothiddendiv" ).trigger( "click" );
assert.equal( called, 1, "Verify that only one click occurred." );
called = 0;
jQuery( "#anchor2" ).trigger( "click" );
assert.equal( called, 1, "Verify that only one click occurred." );
// Make sure that only one callback is removed
jQuery( "#body" ).off( "click", "#anchor2", callback );
called = 0;
jQuery( "#nothiddendiv" ).trigger( "click" );
assert.equal( called, 1, "Verify that only one click occurred." );
called = 0;
jQuery( "#anchor2" ).trigger( "click" );
assert.equal( called, 0, "Verify that no click occurred." );
// Make sure that it still works if the selector is the same,
// but the event type is different
jQuery( "#body" ).on( "foo", "#nothiddendiv", callback );
// Cleanup
jQuery( "#body" ).off( "click", "#nothiddendiv", callback );
called = 0;
jQuery( "#nothiddendiv" ).trigger( "click" );
assert.equal( called, 0, "Verify that no click occurred." );
called = 0;
jQuery( "#nothiddendiv" ).trigger( "foo" );
assert.equal( called, 1, "Verify that one foo occurred." );
// Cleanup
jQuery( "#body" ).off( "foo", "#nothiddendiv", callback );
// Make sure we don't loose the target by DOM modifications
// after the bubble already reached the liveHandler
livec = 0;
jQuery( "#nothiddendivchild" ).html( "<span></span>" );
jQuery( "#body" ).on( "click", "#nothiddendivchild", function() {
jQuery( "#nothiddendivchild" ).html( "" );
} );
jQuery( "#body" ).on( "click", "#nothiddendivchild", function( e ) {
if ( e.target ) {
livec++;
}
} );
jQuery( "#nothiddendiv span" ).trigger( "click" );
assert.equal( jQuery( "#nothiddendiv span" ).length, 0, "Verify that first handler occurred and modified the DOM." );
assert.equal( livec, 1, "Verify that second handler occurred even with nuked target." );
// Cleanup
jQuery( "#body" ).off( "click", "#nothiddendivchild" );
2013-02-27 20:44:34 +00:00
// Verify that .live() occurs and cancel bubble in the same order as
// we would expect .on() and .click() without delegation
lived = 0;
livee = 0;
// bind one pair in one order
jQuery( "#body" ).on( "click", "span#liveSpan1 a", function() {
lived++;
return false;
} );
jQuery( "#body" ).on( "click", "span#liveSpan1", function() {
livee++;
} );
jQuery( "span#liveSpan1 a" ).trigger( "click" );
assert.equal( lived, 1, "Verify that only one first handler occurred." );
assert.equal( livee, 0, "Verify that second handler doesn't." );
// and one pair in inverse
jQuery( "#body" ).on( "click", "span#liveSpan2", function() {
livee++;
} );
jQuery( "#body" ).on( "click", "span#liveSpan2 a", function() {
lived++;
return false;
} );
lived = 0;
livee = 0;
jQuery( "span#liveSpan2 a" ).trigger( "click" );
assert.equal( lived, 1, "Verify that only one first handler occurred." );
assert.equal( livee, 0, "Verify that second handler doesn't." );
// Cleanup
jQuery( "#body" ).off( "click", "**" );
// Test this, target and currentTarget are correct
jQuery( "#body" ).on( "click", "span#liveSpan1", function( e ) {
assert.equal( this.id, "liveSpan1", "Check the this within a on handler" );
assert.equal( e.currentTarget.id, "liveSpan1", "Check the event.currentTarget within a on handler" );
assert.equal( e.delegateTarget, document.body, "Check the event.delegateTarget within a on handler" );
assert.equal( e.target.nodeName.toUpperCase(), "A", "Check the event.target within a on handler" );
} );
jQuery( "span#liveSpan1 a" ).trigger( "click" );
jQuery( "#body" ).off( "click", "span#liveSpan1" );
// Work with deep selectors
livee = 0;
function clickB() {
livee++;
}
jQuery( "#body" ).on( "click", "#nothiddendiv div", function() {
livee++;
} );
jQuery( "#body" ).on( "click", "#nothiddendiv div", clickB );
jQuery( "#body" ).on( "mouseover", "#nothiddendiv div", function() {
livee++;
} );
assert.equal( livee, 0, "No clicks, deep selector." );
livee = 0;
jQuery( "#nothiddendivchild" ).trigger( "click" );
assert.equal( livee, 2, "Click, deep selector." );
livee = 0;
jQuery( "#nothiddendivchild" ).trigger( "mouseover" );
assert.equal( livee, 1, "Mouseover, deep selector." );
jQuery( "#body" ).off( "mouseover", "#nothiddendiv div" );
livee = 0;
jQuery( "#nothiddendivchild" ).trigger( "click" );
assert.equal( livee, 2, "Click, deep selector." );
livee = 0;
jQuery( "#nothiddendivchild" ).trigger( "mouseover" );
assert.equal( livee, 0, "Mouseover, deep selector." );
jQuery( "#body" ).off( "click", "#nothiddendiv div", clickB );
livee = 0;
jQuery( "#nothiddendivchild" ).trigger( "click" );
assert.equal( livee, 1, "Click, deep selector." );
jQuery( "#body" ).off( "click", "#nothiddendiv div" );
} );
QUnit.test( "jQuery.off using dispatched jQuery.Event", function( assert ) {
assert.expect( 1 );
var markup = jQuery( "<p><a href='#'>target</a></p>" ),
count = 0;
markup
.on( "click.name", "a", function( event ) {
assert.equal( ++count, 1, "event called once before removal" );
jQuery().off( event );
} )
.find( "a" ).trigger( "click" ).trigger( "click" ).end()
.remove();
} );
QUnit.test( "events with type matching an Object.prototype property (gh-3256)", function( assert ) {
assert.expect( 1 );
var elem = jQuery( "<div></div>" ),
eventFired = false;
elem.appendTo( "#qunit-fixture" );
try {
elem
.one( "hasOwnProperty", function() {
eventFired = true;
} )
.trigger( "hasOwnProperty" );
} finally {
assert.strictEqual( eventFired, true, "trigger fired without crashing" );
}
} );
QUnit.test( "events with type matching an Object.prototype property, cloned element (gh-3256)",
function( assert ) {
assert.expect( 1 );
var elem = jQuery( "<div></div>" ),
eventFired = false;
elem.appendTo( "#qunit-fixture" );
try {
// Make sure the original element has some event data.
elem.on( "click", function() {} );
elem
.clone( true )
.one( "hasOwnProperty", function() {
eventFired = true;
} )
.trigger( "hasOwnProperty" );
} finally {
assert.strictEqual( eventFired, true, "trigger fired without crashing" );
}
} );
// selector-native does not support scope-fixing in delegation
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "delegated event with delegateTarget-relative selector", function( assert ) {
assert.expect( 3 );
var markup = jQuery( "<div><ul><li><a id=\"a0\"></a><ul id=\"ul0\"><li class=test><a id=\"a0_0\"></a></li><li><a id=\"a0_1\"></a></li></ul></li></ul></div>" ).appendTo( "#qunit-fixture" );
// Non-positional selector (trac-12383)
markup.find( "#ul0" )
.on( "click", "div li a", function() {
assert.ok( false, "div is ABOVE the delegation point!" );
} )
.on( "click", "ul a", function() {
assert.ok( false, "ul IS the delegation point!" );
} )
.on( "click", "li.test a", function() {
assert.ok( true, "li.test is below the delegation point." );
} )
.find( "#a0_0" ).trigger( "click" ).end()
.off( "click" );
if ( QUnit.jQuerySelectorsPos ) {
// Positional selector (trac-11315)
markup.find( "ul" ).eq( 0 )
.on( "click", ">li>a", function() {
assert.ok( this.id === "a0", "child li was clicked" );
} )
.find( "#ul0" )
.on( "click", "li:first>a", function() {
assert.ok( this.id === "a0_0", "first li under #u10 was clicked" );
} )
.end()
.find( "a" ).trigger( "click" ).end()
.find( "#ul0" ).off();
} else {
assert.ok( "skip", "Positional selectors are not supported" );
assert.ok( "skip", "Positional selectors are not supported" );
}
markup.remove();
} );
QUnit.test( "delegated event with selector matching Object.prototype property (trac-13203)", function( assert ) {
assert.expect( 1 );
var matched = 0;
jQuery( "#foo" ).on( "click", "toString", function() {
matched++;
} );
jQuery( "#anchor2" ).trigger( "click" );
assert.equal( matched, 0, "Nothing matched 'toString'" );
} );
QUnit.test( "delegated event with intermediate DOM manipulation (trac-13208)", function( assert ) {
assert.expect( 1 );
jQuery( "#foo" ).on( "click", "[id=sap]", function() {} );
jQuery( "#sap" ).on( "click", "[id=anchor2]", function() {
document.createDocumentFragment().appendChild( this.parentNode );
assert.ok( true, "Element removed" );
} );
jQuery( "#anchor2" ).trigger( "click" );
} );
QUnit.test( "ignore comment nodes in event delegation (gh-2055)", function( assert ) {
assert.expect( 1 );
// Test if DOMNodeInserted is supported
// This is a back-up for when DOMNodeInserted support
// is eventually removed from browsers
function test() {
var ret = false;
var $fixture = jQuery( "#qunit-fixture" );
$fixture.on( "DOMNodeInserted", function() {
ret = true;
$fixture.off( "DOMNodeInserted" );
} ).append( "<div></div>" );
return ret;
}
var $foo = jQuery( "#foo" ).on( "DOMNodeInserted", "[id]", function() {
assert.ok( true, "No error thrown on comment node" );
} ),
$comment = jQuery( document.createComment( "comment" ) )
.appendTo( $foo.find( "#sap" ) );
if ( !test() ) {
fireNative( $comment[ 0 ], "DOMNodeInserted" );
}
} );
QUnit.test( "stopPropagation() stops directly-bound events on delegated target", function( assert ) {
assert.expect( 1 );
var markup = jQuery( "<div><p><a href=\"#\">target</a></p></div>" );
markup
.on( "click", function() {
assert.ok( false, "directly-bound event on delegate target was called" );
} )
.on( "click", "a", function( e ) {
e.stopPropagation();
assert.ok( true, "delegated handler was called" );
} )
.find( "a" ).trigger( "click" ).end()
.remove();
} );
QUnit.test( "off all bound delegated events", function( assert ) {
assert.expect( 2 );
var count = 0,
clicks = 0,
div = jQuery( "#body" );
div.on( "click submit", "div#nothiddendivchild", function() {
count++;
} );
div.on( "click", function() {
clicks++;
} );
div.off( undefined, "**" );
jQuery( "div#nothiddendivchild" ).trigger( "click" );
jQuery( "div#nothiddendivchild" ).trigger( "submit" );
assert.equal( count, 0, "Make sure no events were triggered." );
div.trigger( "click" );
assert.equal( clicks, 2, "Make sure delegated and directly bound event occurred." );
div.off( "click" );
} );
QUnit.test( "on with multiple delegated events", function( assert ) {
assert.expect( 1 );
var count = 0,
div = jQuery( "#body" );
div.on( "click submit", "div#nothiddendivchild", function() {
count++;
} );
jQuery( "div#nothiddendivchild" ).trigger( "click" );
jQuery( "div#nothiddendivchild" ).trigger( "submit" );
assert.equal( count, 2, "Make sure both the click and submit were triggered." );
jQuery( "#body" ).off( undefined, "**" );
} );
QUnit.test( "delegated on with change", function( assert ) {
assert.expect( 8 );
var select, checkbox, checkboxFunction,
text, textChange, oldTextVal,
password, passwordChange, oldPasswordVal,
selectChange = 0,
checkboxChange = 0;
select = jQuery( "select[name='S1']" );
jQuery( "#body" ).on( "change", "select[name='S1']", function() {
selectChange++;
} );
checkbox = jQuery( "#check2" );
checkboxFunction = function() {
checkboxChange++;
};
jQuery( "#body" ).on( "change", "#check2", checkboxFunction );
// test click on select
// second click that changed it
selectChange = 0;
select[ 0 ].selectedIndex = select[ 0 ].selectedIndex ? 0 : 1;
select.trigger( "change" );
assert.equal( selectChange, 1, "Change on click." );
// test keys on select
selectChange = 0;
select[ 0 ].selectedIndex = select[ 0 ].selectedIndex ? 0 : 1;
select.trigger( "change" );
assert.equal( selectChange, 1, "Change on keyup." );
// test click on checkbox
checkbox.trigger( "change" );
assert.equal( checkboxChange, 1, "Change on checkbox." );
// test blur/focus on text
text = jQuery( "#name" );
textChange = 0;
oldTextVal = text.val();
jQuery( "#body" ).on( "change", "#name", function() {
textChange++;
} );
text.val( oldTextVal + "foo" );
text.trigger( "change" );
assert.equal( textChange, 1, "Change on text input." );
text.val( oldTextVal );
jQuery( "#body" ).off( "change", "#name" );
// test blur/focus on password
password = jQuery( "#name" );
passwordChange = 0;
oldPasswordVal = password.val();
jQuery( "#body" ).on( "change", "#name", function() {
passwordChange++;
} );
password.val( oldPasswordVal + "foo" );
password.trigger( "change" );
assert.equal( passwordChange, 1, "Change on password input." );
password.val( oldPasswordVal );
jQuery( "#body" ).off( "change", "#name" );
// make sure die works
// die all changes
selectChange = 0;
jQuery( "#body" ).off( "change", "select[name='S1']" );
select[ 0 ].selectedIndex = select[ 0 ].selectedIndex ? 0 : 1;
select.trigger( "change" );
assert.equal( selectChange, 0, "Die on click works." );
selectChange = 0;
select[ 0 ].selectedIndex = select[ 0 ].selectedIndex ? 0 : 1;
select.trigger( "change" );
assert.equal( selectChange, 0, "Die on keyup works." );
// die specific checkbox
jQuery( "#body" ).off( "change", "#check2", checkboxFunction );
checkbox.trigger( "change" );
assert.equal( checkboxChange, 1, "Die on checkbox." );
} );
QUnit.test( "delegated on with submit", function( assert ) {
assert.expect( 2 );
var count1 = 0, count2 = 0;
jQuery( "#body" ).on( "submit", "#testForm", function( ev ) {
count1++;
ev.preventDefault();
} );
jQuery( document ).on( "submit", "body", function( ev ) {
count2++;
ev.preventDefault();
} );
jQuery( "#testForm input[name=sub1]" ).trigger( "submit" );
assert.equal( count1, 1, "Verify form submit." );
assert.equal( count2, 1, "Verify body submit." );
jQuery( "#body" ).off( undefined, "**" );
jQuery( document ).off( undefined, "**" );
} );
QUnit.test( "delegated off() with only namespaces", function( assert ) {
assert.expect( 2 );
2011-04-05 22:12:50 +00:00
var $delegate = jQuery( "#liveHandlerOrder" ),
count = 0;
2011-04-05 22:12:50 +00:00
$delegate.on( "click.ns", "a", function() {
2011-04-05 22:12:50 +00:00
count++;
} );
2011-04-05 22:12:50 +00:00
jQuery( "a", $delegate ).eq( 0 ).trigger( "click.ns" );
2011-04-05 22:12:50 +00:00
assert.equal( count, 1, "delegated click.ns" );
2011-04-05 22:12:50 +00:00
$delegate.off( ".ns", "**" );
2011-04-05 22:12:50 +00:00
jQuery( "a", $delegate ).eq( 1 ).trigger( "click.ns" );
2011-04-05 22:12:50 +00:00
assert.equal( count, 1, "no more .ns after off" );
} );
2011-04-05 22:12:50 +00:00
QUnit.test( "Non DOM element events", function( assert ) {
assert.expect( 1 );
var o = {};
jQuery( o ).on( "nonelementobj", function() {
assert.ok( true, "Event on non-DOM object triggered" );
} );
jQuery( o ).trigger( "nonelementobj" ).off( "nonelementobj" );
} );
QUnit.test( "inline handler returning false stops default", function( assert ) {
assert.expect( 1 );
var markup = jQuery( "<div><a href=\"#\" onclick=\"return false\">x</a></div>" );
markup.on( "click", function( e ) {
assert.ok( e.isDefaultPrevented(), "inline handler prevented default" );
return false;
} );
markup.find( "a" ).trigger( "click" );
markup.off( "click" );
} );
QUnit.test( "window resize", function( assert ) {
assert.expect( 2 );
jQuery( window ).off();
2010-10-11 22:03:54 +00:00
jQuery( window ).on( "resize", function() {
assert.ok( true, "Resize event fired." );
} ).trigger( "resize" ).off( "resize" );
assert.ok( !jQuery._data( window, "events" ), "Make sure all the events are gone." );
} );
QUnit.test( "focusin bubbles", function( assert ) {
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
assert.expect( 3 );
2011-04-12 11:12:58 +00:00
var input = jQuery( "<input type='text' />" ).prependTo( "body" ),
order = 0;
// focus the element so DOM focus won't fire
input[ 0 ].focus();
jQuery( "body" ).on( "focusin.focusinBubblesTest", function() {
assert.equal( 1, order++, "focusin on the body second" );
} );
input.on( "focusin.focusinBubblesTest", function() {
assert.equal( 0, order++, "focusin on the element first" );
} );
// DOM focus method
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
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.
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
order = 1;
jQuery( "#text1" )[ 0 ].focus();
// jQuery trigger, which calls DOM focus
order = 0;
input.trigger( "focus" );
input.remove();
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" );
} );
// DOM focus method
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
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.
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
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 );
var tab = jQuery( "<table><tr><td>trigger</td></tr></table>" ).appendTo( "body" );
try {
tab.trigger( "back:forth" );
assert.ok( true, "colon events don't throw" );
} catch ( e ) {
assert.ok( false, "colon events die" );
2012-06-21 19:30:24 +00:00
}
tab.remove();
} );
QUnit.test( ".on and .off", function( assert ) {
assert.expect( 9 );
var counter, mixfn, data,
$onandoff = jQuery( "<div id=\"onandoff\"><p>on<b>and</b>off</p><div>worked<em>or</em>borked?</div></div>" ).appendTo( "body" );
// Simple case
jQuery( "#onandoff" )
.on( "whip", function() {
assert.ok( true, "whipped it good" );
} )
.trigger( "whip" )
.off();
// Direct events only
counter = 0;
2011-07-29 14:20:09 +00:00
jQuery( "#onandoff b" )
.on( "click", 5, function( e, trig ) {
counter += e.data + ( trig || 9 ); // twice, 5+9+5+17=36
} )
.one( "click", 7, function( e, trig ) {
counter += e.data + ( trig || 11 ); // once, 7+11=18
} )
.trigger( "click" )
.trigger( "click", 17 )
.off( "click" );
assert.equal( counter, 54, "direct event bindings with data" );
// Delegated events only
counter = 0;
jQuery( "#onandoff" )
.on( "click", "em", 5, function( e, trig ) {
counter += e.data + ( trig || 9 ); // twice, 5+9+5+17=36
} )
.one( "click", "em", 7, function( e, trig ) {
counter += e.data + ( trig || 11 ); // once, 7+11=18
} )
.find( "em" )
.trigger( "click" )
.trigger( "click", 17 )
.end()
.off( "click", "em" );
assert.equal( counter, 54, "delegated event bindings with data" );
// Mixed event bindings and types
counter = 0;
mixfn = function( e, trig ) {
counter += ( e.data || 0 ) + ( trig || 1 );
};
jQuery( "#onandoff" )
.on( " click clack cluck ", "em", 2, mixfn )
.on( "cluck", "b", 7, mixfn )
.on( "cluck", mixfn )
.trigger( "what!" )
.each( function() {
assert.equal( counter, 0, "nothing triggered yet" );
} )
.find( "em" )
.one( "cluck", 3, mixfn )
.trigger( "cluck", 8 ) // 3+8 2+8 + 0+8 = 29
.off()
.trigger( "cluck", 9 ) // 2+9 + 0+9 = 20
.end()
.each( function() {
assert.equal( counter, 49, "after triggering em element" );
} )
.off( "cluck", function() {} ) // shouldn't remove anything
.trigger( "cluck", 2 ) // 0+2 = 2
.each( function() {
assert.equal( counter, 51, "after triggering #onandoff cluck" );
} )
.find( "b" )
.on( "click", 95, mixfn )
.on( "clack", "p", 97, mixfn )
.one( "cluck", 3, mixfn )
.trigger( "quack", 19 ) // 0
.off( "click clack cluck" )
.end()
.each( function() {
assert.equal( counter, 51, "after triggering b" );
} )
.trigger( "cluck", 3 ) // 0+3 = 3
2011-07-29 14:20:09 +00:00
.off( "clack", "em", mixfn )
.find( "em" )
.trigger( "clack" ) // 0
.end()
.each( function() {
assert.equal( counter, 54, "final triggers" );
} )
.off( "click cluck" );
// We should have removed all the event handlers ... kinda hacky way to check this
data = jQuery.data[ jQuery( "#onandoff" )[ 0 ].expando ] || {};
assert.equal( data.events, undefined, "no events left" );
$onandoff.remove();
} );
QUnit.test( "special on name mapping", function( assert ) {
assert.expect( 7 );
jQuery.event.special.slap = {
bindType: "click",
delegateType: "swing",
handle: function( event ) {
assert.equal( event.handleObj.origType, "slap", "slapped your mammy, " + event.type );
}
};
var comeback = function( event ) {
assert.ok( true, "event " + event.type + " triggered" );
};
jQuery( "<div><button id=\"mammy\">Are We Not Men?</button></div>" )
.on( "slap", "button", jQuery.noop )
.on( "swing", "button", comeback )
.find( "button" )
.on( "slap", jQuery.noop )
.on( "click", comeback )
.trigger( "click" ) // bindType-slap and click
.off( "slap" )
.trigger( "click" ) // click
.off( "click" )
.trigger( "swing" ) // delegateType-slap and swing
.end()
.off( "slap swing", "button" )
.find( "button" ) // everything should be gone
.trigger( "slap" )
.trigger( "click" )
.trigger( "swing" )
.end()
.remove();
delete jQuery.event.special.slap;
jQuery.event.special.gutfeeling = {
bindType: "click",
delegateType: "click",
handle: function( event ) {
assert.equal( event.handleObj.origType, "gutfeeling", "got a gutfeeling" );
// Need to call the handler since .one() uses it to unbind
return event.handleObj.handler.call( this, event );
}
};
// Ensure a special event isn't removed by its mapped type
jQuery( "<p>Gut Feeling</p>" )
.on( "click", jQuery.noop )
.on( "gutfeeling", jQuery.noop )
.off( "click" )
.trigger( "gutfeeling" )
.remove();
// Ensure special events are removed when only a namespace is provided
jQuery( "<p>Gut Feeling</p>" )
.on( "gutfeeling.Devo", jQuery.noop )
.off( ".Devo" )
.trigger( "gutfeeling" )
.remove();
// Ensure .one() events are removed after their maiden voyage
jQuery( "<p>Gut Feeling</p>" )
.one( "gutfeeling", jQuery.noop )
.trigger( "gutfeeling" ) // This one should
.trigger( "gutfeeling" ) // This one should not
.remove();
delete jQuery.event.special.gutfeeling;
} );
QUnit.test( ".on and .off, selective mixed removal (trac-10705)", function( assert ) {
assert.expect( 7 );
var timingx = function( e ) {
assert.ok( true, "triggered " + e.type );
};
jQuery( "<p>Strange Pursuit</p>" )
.on( "click", timingx )
.on( "click.duty", timingx )
.on( "click.now", timingx )
.on( "devo", timingx )
.on( "future", timingx )
.trigger( "click" ) // 3
.trigger( "devo" ) // 1
.off( ".duty devo " ) // trailing space
.trigger( "future" ) // 1
.trigger( "click" ) // 2
.off( "future click" )
.trigger( "click" ); // 0
} );
QUnit.test( "special interference with Object.prototype", function( assert ) {
assert.expect( 1 );
var triggered = false;
Object.prototype.jqfake = {
trigger: function() {
triggered = true;
}
};
jQuery( "<div></div>" )
.appendTo( "#qunit-fixture" )
.trigger( "jqfake" );
delete Object.prototype.jqfake;
assert.ok( !triggered, "Object.prototype.jqfake.trigger not called" );
} );
QUnit.test( ".on( event-map, null-selector, data ) trac-11130", function( assert ) {
assert.expect( 1 );
var $p = jQuery( "<p>Strange Pursuit</p>" ),
data = "bar",
map = {
"foo": function( event ) {
assert.equal( event.data, "bar", "event.data correctly relayed with null selector" );
$p.remove();
}
};
$p.on( map, null, data ).trigger( "foo" );
} );
QUnit.test( "clone() delegated events (trac-11076)", function( assert ) {
assert.expect( 3 );
var counter = { "center": 0, "fold": 0, "centerfold": 0 },
clicked = function() {
counter[ jQuery( this ).text().replace( /\s+/, "" ) ]++;
},
table =
jQuery( "<table><tr><td>center</td><td>fold</td></tr></table>" )
.on( "click", "tr", clicked )
.on( "click", "td:first-child", clicked )
.on( "click", "td:last-child", clicked ),
clone = table.clone( true );
clone.find( "td" ).trigger( "click" );
assert.equal( counter.center, 1, "first child" );
assert.equal( counter.fold, 1, "last child" );
assert.equal( counter.centerfold, 2, "all children" );
table.remove();
clone.remove();
} );
QUnit.test( "checkbox state (trac-3827)", function( assert ) {
assert.expect( 16 );
var markup = jQuery( "<div class='parent'><input type=checkbox><div>" ),
cb = markup.find( "input" )[ 0 ];
markup.appendTo( "#qunit-fixture" );
jQuery( cb ).on( "click", function() {
assert.equal( this.checked, false, "just-clicked checkbox is not checked" );
} );
markup.on( "click", function() {
assert.equal( cb.checked, false, "checkbox is not checked in bubbled event" );
} );
// Native click
cb.checked = true;
assert.equal( cb.checked, true, "native event - checkbox is initially checked" );
cb.click();
assert.equal( cb.checked, false, "native event - checkbox is no longer checked" );
// jQuery click
cb.checked = true;
assert.equal( cb.checked, true, "jQuery event - checkbox is initially checked" );
jQuery( cb ).trigger( "click" );
assert.equal( cb.checked, false, "jQuery event - checkbox is no longer checked" );
// Handlers only; checkbox state remains false
jQuery( cb ).triggerHandler( "click" );
assert.equal( cb.checked, false, "handlers only - checkbox is still unchecked" );
// Trigger parameters are preserved (trac-13353, gh-4139)
cb.checked = true;
assert.equal( cb.checked, true, "jQuery event with data - checkbox is initially checked" );
jQuery( cb ).on( "click", function( e, data ) {
assert.equal( data, "clicked", "trigger data passed to handler" );
} );
markup.on( "click", function( e, data ) {
assert.equal( data, "clicked", "trigger data passed to bubbled handler" );
} );
jQuery( cb ).trigger( "click", [ "clicked" ] );
assert.equal( cb.checked, false, "jQuery event with data - checkbox is no longer checked" );
} );
QUnit.test( "event object properties on natively-triggered event", function( assert ) {
assert.expect( 3 );
var link = document.createElement( "a" ),
$link = jQuery( link ),
evt = document.createEvent( "MouseEvents" );
// Support: IE <=9 - 11+
// IE requires element to be in the body before it will dispatch
$link.appendTo( "body" ).on( "click", function( e ) {
// Not trying to assert specific values here, just ensure the property exists
assert.equal( "detail" in e, true, "has .detail" );
assert.equal( "cancelable" in e, true, "has .cancelable" );
assert.equal( "bubbles" in e, true, "has .bubbles" );
} );
evt.initEvent( "click", true, true );
link.dispatchEvent( evt );
$link.off( "click" ).remove();
} );
QUnit.test( "addProp extensions", function( assert ) {
assert.expect( 2 );
var $fixture = jQuery( "<div>" ).appendTo( "#qunit-fixture" );
// Ensure the property doesn't exist
$fixture.on( "click", function( event ) {
assert.ok( !( "testProperty" in event ), "event.testProperty does not exist" );
} );
fireNative( $fixture[ 0 ], "click" );
$fixture.off( "click" );
2011-09-26 17:00:45 +00:00
jQuery.event.addProp( "testProperty", function() {
return 42;
} );
2011-09-26 17:00:45 +00:00
// Trigger a native click and ensure the property is set
$fixture.on( "click", function( event ) {
assert.equal( event.testProperty, 42, "event.testProperty getter was invoked" );
} );
fireNative( $fixture[ 0 ], "click" );
$fixture.off( "click" );
$fixture.remove();
} );
QUnit.test( "drag/drop events copy mouse-related event properties (gh-1925, gh-2009)", function( assert ) {
assert.expect( 4 );
var $fixture = jQuery( "<div id='drag-fixture'></div>" ).appendTo( "body" );
$fixture.on( "dragmove", function( evt ) {
assert.ok( "pageX" in evt, "checking for pageX property on dragmove" );
assert.ok( "pageY" in evt, "checking for pageY property on dragmove" );
} );
fireNative( $fixture[ 0 ], "dragmove" );
$fixture.on( "drop", function( evt ) {
assert.ok( "pageX" in evt, "checking for pageX property on drop" );
assert.ok( "pageY" in evt, "checking for pageY property on drop" );
} );
fireNative( $fixture[ 0 ], "drop" );
$fixture.off( "dragmove drop" ).remove();
} );
QUnit.test( "focusin using non-element targets", function( assert ) {
assert.expect( 2 );
jQuery( document ).on( "focusin", function( e ) {
assert.ok( e.type === "focusin", "got a focusin event on a document" );
} ).trigger( "focusin" ).off( "focusin" );
jQuery( window ).on( "focusin", function( e ) {
assert.ok( e.type === "focusin", "got a focusin event on a window" );
} ).trigger( "focusin" ).off( "focusin" );
} );
testIframe(
"focusin from an iframe",
"event/focusinCrossFrame.html",
function( assert, framejQuery, frameWin, frameDoc ) {
assert.expect( 1 );
var done = assert.async(),
input = jQuery( frameDoc ).find( "#frame-input" );
// Create a focusin handler on the parent; shouldn't affect the iframe's fate
jQuery( "body" ).on( "focusin.iframeTest", function() {
// Support: IE 9 - 11+
// IE does propagate the event to the parent document. In this test
// we mainly care about the inner element so we'll just skip this one
// assertion in IE.
if ( !document.documentMode ) {
assert.ok( false, "fired a focusin event in the parent document" );
}
} );
input.on( "focusin", function() {
focus = true;
assert.ok( true, "fired a focusin event in the iframe" );
} );
// Avoid a native event; Chrome can't force focus to another frame
input[ 0 ].focus();
// Remove body handler manually since it's outside the fixture
jQuery( "body" ).off( "focusin.iframeTest" );
Tests: migrate testing infrastructure to minimal dependencies This is a complete rework of our testing infrastructure. The main goal is to modernize and drop deprecated or undermaintained dependencies (specifically, grunt, karma, and testswarm). We've achieved that by limiting our dependency list to ones that are unlikely to drop support any time soon. The new dependency list includes: - `qunit` (our trusty unit testing library) - `selenium-webdriver` (for spinning up local browsers) - `express` (for starting a test server and adding middleware) - express middleware includes uses of `body-parser` and `raw-body` - `yargs` (for constructing a CLI with pretty help text) - BrowserStack (for running each of our QUnit modules separately in all of our supported browsers) - `browserstack-local` (for opening a local tunnel. This is the same package still currently used in the new Browserstack SDK) - We are not using any other BrowserStack library. The newest BrowserStack SDK does not fit our needs (and isn't open source). Existing libraries, such as `node-browserstack` or `browserstack-runner`, either do not quite fit our needs, are under-maintained and out-of-date, or are not robust enough to meet all of our requirements. We instead call the [BrowserStack REST API](https://github.com/browserstack/api) directly. ## BrowserStack Runner - automatically retries individual modules in case of test failure(s) - automatically attempts to re-establish broken tunnels - automatically refreshes the page in case a test run has stalled - runs all browsers concurrently and uses as many sessions as are available under the BrowserStack plan. It will wait for available sessions if there are none. - supports filtering the available list of browsers by browser name, browser version, device, OS, and OS version (see `npm run test:unit -- --list-browsers` for more info). It will retrieve the latest matching browser available if any of those parameters are not specified. - cleans up after itself (closes the local tunnel, stops the test server, etc.) - Requires `BROWSERSTACK_USERNAME` and `BROWSERSTACK_ACCESS_KEY` environment variables. ## Selenium Runner - supports running any local browser as long as the driver is installed, including support for headless mode in Chrome, FF, and Edge - supports running `basic` tests on the latest [jsdom](https://github.com/jsdom/jsdom#readme), which can be seen in action in this PR (see `test:browserless`) - Node tests will run as before in PRs and all non-dependabot branches, but now includes tests on real Safari in a GH actions macos image instead of playwright-webkit. - can run multiple browsers and multiple modules concurrently Other notes: - Stale dependencies have been removed and all remaining dependencies have been upgraded with a few exceptions: - `sinon`: stopped supporting IE in version 10. But, `sinon` has been updated to 9.x. - `husky`: latest does not support Node 10 and runs on `npm install`. Needed for now until git builds are migrated to GitHub Actions. - `rollup`: latest does not support Node 10. Needed for now until git builds are migrated to GitHub Actions. - BrowserStack tests are set to run on each `main` branch commit - `debug` mode leaves Selenium browsers open whether they pass or fail and leaves browsers with test failures open on BrowserStack. The latter is to avoid leaving open too many sessions. - This PR includes a workflow to dispatch BrowserStack runs on-demand - The Node version used for most workflow tests has been upgraded to 20.x - updated supportjQuery to 3.7.1 Run `npm run test:unit -- --help` for CLI documentation Close gh-5418
2024-02-26 14:42:10 +00:00
setTimeout( done, 50 );
}
);
QUnit.test( "focusin on document & window", function( assert ) {
assert.expect( 1 );
var counter = 0,
input = jQuery( "<input />" );
function increment() {
counter++;
}
input.appendTo( "#qunit-fixture" );
input[ 0 ].focus();
jQuery( window ).on( "focusout", increment );
jQuery( document ).on( "focusout", increment );
input[ 0 ].blur();
assert.strictEqual( counter, 2,
"focusout handlers on document/window fired once only" );
jQuery( window ).off( "focusout", increment );
jQuery( document ).off( "focusout", increment );
} );
QUnit.test( "element removed during focusout (gh-4417)", function( assert ) {
assert.expect( 1 );
var button = jQuery( "<button>Click me</button>" );
button.appendTo( "#qunit-fixture" );
button.on( "click", function() {
button.trigger( "blur" );
assert.ok( true, "Removing the element didn't crash" );
} );
// Support: Chrome 86+
// In Chrome, if an element having a focusout handler is blurred by
// clicking outside of it, it invokes the handler synchronously. However,
// if the click happens programmatically, the invocation is asynchronous.
// As we have no way to simulate real user input in unit tests, simulate
// this behavior by calling `jQuery.cleanData` & removing the element using
// native APIs.
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();
} );
testIframe(
"jQuery.ready promise",
"event/promiseReady.html",
function( assert, jQuery, window, document, isOk ) {
assert.expect( 1 );
assert.ok( isOk, "$.when( $.ready ) works" );
},
jQuery.when ? QUnit.test : QUnit.skip
);
// need PHP here to make the incepted IFRAME hang
if ( hasPHP ) {
testIframe(
"jQuery.ready uses interactive",
"event/interactiveReady.html",
function( assert, jQuery, window, document, isOk ) {
assert.expect( 1 );
assert.ok( isOk, "jQuery fires ready when the DOM can truly be interacted with" );
}
);
}
testIframe(
"Focusing iframe element",
"event/focusElem.html",
function( assert, jQuery, window, document, isOk ) {
assert.expect( 1 );
assert.ok( isOk, "Focused an element in an iframe" );
}
);
testIframe(
"triggerHandler(onbeforeunload)",
"event/triggerunload.html",
function( assert, jQuery, window, document, isOk ) {
assert.expect( 1 );
assert.ok( isOk, "Triggered onbeforeunload without an error" );
}
);
// need PHP here to make the incepted IFRAME hang
if ( hasPHP ) {
testIframe(
"jQuery.ready synchronous load with long loading subresources",
"event/syncReady.html",
function( assert, jQuery, window, document, isOk ) {
assert.expect( 1 );
assert.ok( isOk, "jQuery loaded synchronously fires ready when the DOM can truly be interacted with" );
}
);
}
QUnit.test( "change handler should be detached from element", function( assert ) {
assert.expect( 2 );
var $fixture = jQuery( "<input type='text' id='change-ie-leak' />" ).appendTo( "body" ),
originRemoveEvent = jQuery.removeEvent,
wrapperRemoveEvent = function( elem, type, handle ) {
assert.equal( "change", type, "Event handler for 'change' event should be removed" );
assert.equal( "change-ie-leak", jQuery( elem ).attr( "id" ), "Event handler for 'change' event should be removed from appropriate element" );
originRemoveEvent( elem, type, handle );
};
jQuery.removeEvent = wrapperRemoveEvent;
$fixture.on( "change", function() {} );
$fixture.off( "change" );
$fixture.remove();
jQuery.removeEvent = originRemoveEvent;
} );
QUnit.test( "trigger click on checkbox, fires change event", function( assert ) {
assert.expect( 1 );
var check = jQuery( "#check2" );
var done = assert.async();
check.on( "change", function() {
// get it?
check.off( "change" );
assert.ok( true, "Change event fired as a result of triggered click" );
done();
} ).trigger( "click" );
} );
QUnit.test( "Namespace preserved when passed an Event (trac-12739)", function( assert ) {
assert.expect( 4 );
var markup = jQuery(
"<div id='parent'><div id='child'></div></div>"
),
triggered = 0,
fooEvent;
markup.find( "div" )
.addBack()
.on( "foo.bar", function( e ) {
if ( !e.handled ) {
triggered++;
e.handled = true;
assert.equal( e.namespace, "bar", "namespace is bar" );
jQuery( e.target ).find( "div" ).each( function() {
jQuery( this ).triggerHandler( e );
} );
}
} )
.on( "foo.bar2", function() {
assert.ok( false, "foo.bar2 called on trigger " + triggered + " id " + this.id );
} );
markup.trigger( "foo.bar" );
markup.trigger( jQuery.Event( "foo.bar" ) );
fooEvent = jQuery.Event( "foo" );
fooEvent.namespace = "bar";
markup.trigger( fooEvent );
markup.remove();
assert.equal( triggered, 3, "foo.bar triggered" );
} );
QUnit.test( "make sure events cloned correctly", function( assert ) {
assert.expect( 18 );
var clone,
fixture = jQuery( "#qunit-fixture" ),
checkbox = jQuery( "#check1" ),
p = jQuery( "#firstp" );
fixture.on( "click change", function( event, result ) {
assert.ok( result, event.type + " on original element is fired" );
} ).on( "click", "#firstp", function( event, result ) {
assert.ok( result, "Click on original child element though delegation is fired" );
} ).on( "change", "#check1", function( event, result ) {
assert.ok( result, "Change on original child element though delegation is fired" );
} );
p.on( "click", function() {
assert.ok( true, "Click on original child element is fired" );
} );
checkbox.on( "change", function() {
assert.ok( true, "Change on original child element is fired" );
} );
fixture.clone().trigger( "click" ).trigger( "change" ); // 0 events should be fired
clone = fixture.clone( true );
clone.find( "p" ).eq( 0 ).trigger( "click", true ); // 3 events should fire
clone.find( "#check1" ).trigger( "change", true ); // 3 events should fire
clone.remove();
clone = fixture.clone( true, true );
clone.find( "p" ).eq( 0 ).trigger( "click", true ); // 3 events should fire
clone.find( "#check1" ).trigger( "change", true ); // 3 events should fire
fixture.off();
p.off();
checkbox.off();
p.trigger( "click" ); // 0 should be fired
checkbox.trigger( "change" ); // 0 should be fired
clone.find( "p" ).eq( 0 ).trigger( "click", true ); // 3 events should fire
clone.find( "#check1" ).trigger( "change", true ); // 3 events should fire
clone.remove();
clone.find( "p" ).eq( 0 ).trigger( "click" ); // 0 should be fired
clone.find( "#check1" ).trigger( "change" ); // 0 events should fire
} );
QUnit.test( "String.prototype.namespace does not cause trigger() to throw (trac-13360)", function( assert ) {
assert.expect( 1 );
var errored = false;
String.prototype.namespace = function() {};
try {
jQuery( "<p>" ).trigger( "foo.bar" );
} catch ( e ) {
errored = true;
}
assert.equal( errored, false, "trigger() did not throw exception" );
delete String.prototype.namespace;
} );
QUnit.test( "Inline event result is returned (trac-13993)", function( assert ) {
assert.expect( 1 );
var result = jQuery( "<p onclick='return 42'>hello</p>" ).triggerHandler( "click" );
assert.equal( result, 42, "inline handler returned value" );
} );
QUnit.test( ".off() removes the expando when there's no more data", function( assert ) {
assert.expect( 2 );
var key,
div = jQuery( "<div></div>" ).appendTo( "#qunit-fixture" );
div.on( "click", false );
div.on( "custom", function() {
assert.ok( true, "Custom event triggered" );
} );
div.trigger( "custom" );
div.off( "click custom" );
// Make sure the expando is gone
for ( key in div[ 0 ] ) {
if ( /^jQuery/.test( key ) ) {
assert.strictEqual(
div[ 0 ][ key ], undefined,
"Expando was not removed when there was no more data"
);
}
}
} );
QUnit.test( "jQuery.Event( src ) does not require a target property", function( assert ) {
assert.expect( 2 );
var event = jQuery.Event( { type: "offtarget" } );
assert.equal( event.type, "offtarget", "correct type" );
assert.equal( event.target, undefined, "no target" );
} );
QUnit.test( "preventDefault() on focusin does not throw exception", function( assert ) {
assert.expect( 1 );
var done = assert.async(),
input = jQuery( "<input/>" ).appendTo( "#form" );
input.on( "focusin", function( event ) {
if ( !done ) {
return;
}
var exceptionCaught;
try {
event.preventDefault();
} catch ( theException ) {
exceptionCaught = theException;
}
assert.strictEqual( exceptionCaught, undefined,
"Preventing default on focusin throws no exception" );
done();
} );
input.trigger( "focus" );
} );
QUnit.test( ".on('focus', fn) on a text node doesn't throw", function( assert ) {
assert.expect( 1 );
jQuery( document.createTextNode( "text" ) )
.on( "focus", function() {} );
assert.ok( true, "No crash" );
} );
QUnit.test( "Donor event interference", function( assert ) {
assert.expect( 8 );
var outer = jQuery(
"<div id='donor-outer'>" +
"<form id='donor-form'>" +
"<input id='donor-input' type='checkbox' />" +
"</form>" +
"</div>"
).appendTo( "#qunit-fixture" ),
input = jQuery( "#donor-input" );
input.on( "click", function( event ) {
assert.equal( event.type, "click", "click event at input" );
assert.ok( !event.isPropagationStopped(), "click event at input is still propagating" );
assert.equal( typeof event.originalEvent, "object",
"click event at input has originalEvent property" );
} );
outer.on( "click", function( event ) {
assert.equal( event.type, "click", "click event at ancestor" );
assert.ok( !event.isPropagationStopped(), "click event at ancestor is still propagating" );
assert.equal( typeof event.originalEvent, "object",
"click event at ancestor has originalEvent property" );
} );
input.on( "change", function( event ) {
assert.equal( event.type, "change", "change event at input" );
assert.equal( typeof event.originalEvent, "object",
"change event at input has originalEvent property" );
event.stopPropagation();
} );
input[ 0 ].click();
} );
QUnit.test(
"simulated events shouldn't forward stopPropagation/preventDefault methods",
function( assert ) {
assert.expect( 3 );
var outer = jQuery(
"<div id='donor-outer'>" +
"<form id='donor-form'>" +
"<input id='donor-input' type='checkbox' />" +
"</form>" +
"</div>"
).appendTo( "#qunit-fixture" ),
input = jQuery( "#donor-input" ),
spy = {};
jQuery( "#donor-form" )
.on( "simulated", function( event ) {
spy.prevent = sinon.stub( event.originalEvent, "preventDefault" );
event.preventDefault();
} )
.on( "simulated", function( event ) {
spy.stop = sinon.stub( event.originalEvent, "stopPropagation" );
event.stopPropagation();
} )
.on( "simulated", function( event ) {
spy.immediate = sinon.stub( event.originalEvent, "stopImmediatePropagation" );
event.stopImmediatePropagation();
} )
.on( "simulated", function() {
assert.ok( false, "simulated event immediate propagation stopped" );
} );
outer
.on( "simulated", function() {
assert.ok( false, "simulated event propagation stopped" );
} );
// Force a simulated event
input[ 0 ].addEventListener( "click", function( nativeEvent ) {
jQuery.event.simulate( "simulated", this, jQuery.event.fix( nativeEvent ) );
} );
input[ 0 ].click();
assert.strictEqual( spy.prevent.called, false, "Native preventDefault not called" );
assert.strictEqual( spy.stop.called, false, "Native stopPropagation not called" );
assert.strictEqual( spy.immediate.called, false,
"Native stopImmediatePropagation not called" );
}
);
QUnit.test( "originalEvent type of simulated event", function( assert ) {
assert.expect( 2 );
var outer = jQuery(
"<div id='donor-outer'>" +
"<form id='donor-form'>" +
"<input id='donor-input' type='text' />" +
"</form>" +
"</div>"
).appendTo( "#qunit-fixture" ),
input = jQuery( "#donor-input" ),
done = assert.async(),
finish = function() {
// Remove jQuery handlers to ensure removal of capturing handlers on the document
outer.off( "focusin" );
done();
};
outer.on( "focusin", function( event ) {
assert.equal( event.type, "focusin", "focusin event at ancestor" );
assert.equal( event.originalEvent.type, "click",
"focus event at ancestor has correct originalEvent type" );
setTimeout( finish );
} );
input[ 0 ].addEventListener( "click", function( nativeEvent ) {
jQuery.event.simulate( "focusin", this, jQuery.event.fix( nativeEvent ) );
} );
input[ 0 ].click();
} );
QUnit.test( "trigger('click') on radio passes extra params", function( assert ) {
assert.expect( 1 );
var $radio = jQuery( "<input type='radio' />" ).appendTo( "#qunit-fixture" )
.on( "click", function( e, data ) {
assert.ok( data, "Trigger data is passed to radio click handler" );
} );
$radio.trigger( "click", [ true ] );
} );
QUnit.test( "focusout/focusin support", function( assert ) {
assert.expect( 6 );
var parent = jQuery( "<div>" ),
input = jQuery( "<input>" ),
inputExternal = jQuery( "<input>" );
parent.append( input );
jQuery( "#qunit-fixture" ).append( parent ).append( inputExternal );
// initially, lose focus
inputExternal[ 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" );
} );
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();
// then lose it
inputExternal[ 0 ].focus();
// cleanup
parent.off();
input.off();
} );
QUnit.test( "focus-blur order (trac-12868)", function( assert ) {
assert.expect( 5 );
var order,
$text = jQuery( "#text1" ),
$radio = jQuery( "#radio1" );
$radio[ 0 ].focus();
$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" );
assert.equal( document.activeElement, $text[ 0 ], "text has focus" );
// Run handlers without native method on an input
order = 1;
$radio.triggerHandler( "focus" );
// Clean up
$text.off();
$radio.off();
} );
QUnit.test( "Event handling works with multiple async focus events (gh-4350)", function( assert ) {
assert.expect( 3 );
var remaining = 3,
input = jQuery( "#name" ),
done = assert.async();
input
.on( "focus", function() {
remaining--;
assert.ok( true, "received focus event, expecting " + remaining + " more" );
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();
}
} )
.on( "blur", function() {
setTimeout( function() {
input.trigger( "focus" );
} );
} );
// gain focus
input.trigger( "focus" );
} );
// 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 );
var parent = supportjQuery(
"<div class='parent'><input type='checkbox'><input type='radio'></div>"
).appendTo( "#qunit-fixture" ),
targets = jQuery( parent[ 0 ].childNodes ),
checkbox = jQuery( targets[ 0 ] ),
data = [ "arg1", "arg2" ],
slice = data.slice;
// click (gh-4139)
assert.strictEqual( targets[ 0 ].checked, false, "checkbox unchecked before click" );
assert.strictEqual( targets[ 1 ].checked, false, "radio unchecked before click" );
targets.add( parent ).on( "click", function( event ) {
var type = event.target.type,
level = event.currentTarget === parent[ 0 ] ? "parent" : "";
assert.strictEqual( event.target.checked, true,
type + " toggled before invoking " + level + " handler" );
assert.deepEqual( slice.call( arguments, 1 ), data,
type + " " + level + " handler received correct data" );
} );
targets.trigger( "click", data );
assert.strictEqual( targets[ 0 ].checked, true,
"checkbox toggled after click (default action)" );
assert.strictEqual( targets[ 1 ].checked, true,
"radio toggled after event (default action)" );
// focus (gh-1741)
assert.notEqual( document.activeElement, checkbox[ 0 ],
"element not focused before focus event" );
checkbox.on( "focus blur", function( event ) {
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 );
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 ) {
assert.expect( 2 );
var done = assert.async(),
select = jQuery( "<select><option selected='selected'>A</option></select>" ),
button = jQuery( "<button>Focus target</button>" );
jQuery( "#qunit-fixture" )
.append( select )
.append( button );
select.on( "focus", function() {
button.trigger( "focus" );
} );
jQuery( document ).on( "focusin.focusTests", function( ev ) {
// Support: IE 11+
// In IE focus is async so focusin on document is fired multiple times,
// for each of the elements. In other browsers it's fired just once, for
// the last one.
if ( ev.target === button[ 0 ] ) {
assert.ok( true, "focusin propagated to document from the button" );
}
} );
select.trigger( "focus" );
setTimeout( function() {
assert.strictEqual( document.activeElement, button[ 0 ], "Focus redirect worked" );
jQuery( document ).off( ".focusTests" );
done();
} );
} );
QUnit.test( "trigger(focus) works after .on(focus).off(focus) (gh-4867)", function( assert ) {
assert.expect( 1 );
var input = jQuery( "<input />" );
input.appendTo( "#qunit-fixture" );
input
.on( "focus", function() {} )
.off( "focus" );
input.trigger( "focus" );
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" );
} );
QUnit.test( "trigger(focus) fires native & jQuery handlers (gh-5015)", function( assert ) {
assert.expect( 3 );
var input = jQuery( "<input />" ),
// Support: IE 9 - 11+
// focus is async in IE; we now emulate it via sync focusin in jQuery
// but this test also attaches native handlers.
done = assert.async( 3 );
input.appendTo( "#qunit-fixture" );
input[ 0 ].addEventListener( "focus", function() {
assert.ok( true, "1st native handler fired" );
done();
} );
input.on( "focus", function() {
assert.ok( true, "jQuery handler fired" );
done();
} );
input[ 0 ].addEventListener( "focus", function() {
assert.ok( true, "2nd native handler fired" );
done();
} );
input.trigger( "focus" );
} );
QUnit.test( "duplicate native blur doesn't crash (gh-5459)", function( assert ) {
assert.expect( 4 );
function patchAddEventListener( elem ) {
var nativeAddEvent = elem[ 0 ].addEventListener;
// Support: Firefox 124+
// In Firefox, alert displayed just before blurring an element
// dispatches the native blur event twice which tripped the jQuery
// logic. We cannot call `alert()` in unit tests; simulate the
// behavior by overwriting the native `addEventListener` with
// a version that calls blur handlers twice.
//
// Such a simulation allows us to test whether `leverageNative`
// logic correctly differentiates between data saved by outer/inner
// handlers, so it's useful even without the Firefox bug.
elem[ 0 ].addEventListener = function( eventName, handler ) {
var finalHandler;
if ( eventName === "blur" ) {
finalHandler = function wrappedHandler() {
handler.apply( this, arguments );
return handler.apply( this, arguments );
};
} else {
finalHandler = handler;
}
return nativeAddEvent.call( this, eventName, finalHandler );
};
}
function runTest( handler, message ) {
var button = jQuery( "<button></button>" );
patchAddEventListener( button );
button.appendTo( "#qunit-fixture" );
if ( handler ) {
button.on( "blur", handler );
}
button.on( "focus", function() {
button.trigger( "blur" );
assert.ok( true, "Did not crash (" + message + ")" );
} );
button.trigger( "focus" );
}
runTest( undefined, "no prior handler" );
runTest( function() {
return true;
}, "prior handler returning true" );
runTest( function() {
return { length: 42 };
}, "prior handler returning an array-like" );
runTest( function() {
return { value: 42 };
}, "prior handler returning `{ value }`" );
} );
// TODO replace with an adaptation of
// https://github.com/jquery/jquery/pull/1367/files#diff-a215316abbaabdf71857809e8673ea28R2464
( function() {
supportjQuery.each(
{
checkbox: "<input type='checkbox'>",
radio: "<input type='radio'>"
},
function( type, html ) {
makeTestForGh3751( type, html );
makeTestForGh5015( type, html );
}
);
function makeTestForGh3751( type, html ) {
var testName = "native-backed namespaced clicks are handled correctly (gh-3751) - " + type;
QUnit.test( testName, function( assert ) {
assert.expect( 2 );
var parent = supportjQuery( "<div class='parent'>" + html + "</div>" ),
target = jQuery( parent[ 0 ].firstChild );
parent.appendTo( "#qunit-fixture" );
target.add( parent )
.on( "click.notFired", function( event ) {
assert.ok( false, "namespaced event should not be received" +
" by wrong-namespace listener at " + event.currentTarget.nodeName );
} )
.on( "click.fired", function( event ) {
assert.equal( event.target.checked, true,
"toggled before invoking handler at " + event.currentTarget.nodeName );
} )
.on( "click", function( event ) {
assert.ok( false, "namespaced event should not be received" +
" by non-namespaced listener at " + event.currentTarget.nodeName );
} );
target.trigger( "click.fired" );
} );
}
function makeTestForGh5015( type, html ) {
var testName = "trigger(click) fires native & jQuery handlers (gh-5015) - " + type;
QUnit.test( testName, function( assert ) {
assert.expect( 3 );
var parent = supportjQuery( "<div class='parent'>" + html + "</div>" ),
input = jQuery( parent[ 0 ].firstChild );
parent.appendTo( "#qunit-fixture" );
input[ 0 ].addEventListener( "click", function() {
assert.ok( true, "1st native handler fired" );
} );
input.on( "click", function() {
assert.ok( true, "jQuery handler fired" );
} );
input[ 0 ].addEventListener( "click", function() {
assert.ok( true, "2nd native handler fired" );
} );
input.trigger( "click" );
} );
}
} )();