From c426b9a203271ab5e5e5f165a1d686c8281164bf Mon Sep 17 00:00:00 2001 From: Konstantin Dinev Date: Tue, 28 Feb 2017 18:21:46 +0200 Subject: Resizable: Keep user-provided handles on destroy Closes gh-1798 Ref gh-1795 --- tests/unit/resizable/options.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'tests/unit') diff --git a/tests/unit/resizable/options.js b/tests/unit/resizable/options.js index 4080bac0e..81206abf8 100644 --- a/tests/unit/resizable/options.js +++ b/tests/unit/resizable/options.js @@ -434,8 +434,10 @@ QUnit.test( "zIndex, applied to all handles", function( assert ) { } ); QUnit.test( "setOption handles", function( assert ) { - assert.expect( 15 ); + assert.expect( 19 ); + // https://bugs.jqueryui.com/ticket/3423 + // https://bugs.jqueryui.com/ticket/15084 var target = $( "
" ).resizable(), target2 = $( "
" + "
" + @@ -470,6 +472,12 @@ QUnit.test( "setOption handles", function( assert ) { target2.resizable( "option", "handles", "e, s, w" ); checkHandles( target2, [ "e", "s", "w" ] ); + + target.resizable( "destroy" ); + checkHandles( target, [ ] ); + + target2.resizable( "destroy" ); + checkHandles( target2, [ "e", "w" ] ); } ); QUnit.test( "alsoResize + containment", function( assert ) { -- cgit v1.2.3 From b3c0a7f71d0b351755b97858ad47de4e9a373606 Mon Sep 17 00:00:00 2001 From: Scott González Date: Fri, 21 Apr 2017 14:49:52 -0400 Subject: Widget: Handle `Object.create(null)` for options objects Fixes #15179 Closes gh-1809 --- tests/unit/widget/extend.js | 7 ++++++- ui/widget.js | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'tests/unit') diff --git a/tests/unit/widget/extend.js b/tests/unit/widget/extend.js index 36575200b..b27d925f0 100644 --- a/tests/unit/widget/extend.js +++ b/tests/unit/widget/extend.js @@ -5,7 +5,7 @@ define( [ ], function( QUnit, $ ) { QUnit.test( "$.widget.extend()", function( assert ) { - assert.expect( 27 ); + assert.expect( 28 ); var ret, empty, optionsWithLength, optionsWithDate, myKlass, customObject, optionsWithCustomObject, nullUndef, target, recursive, obj, input, output, @@ -108,6 +108,11 @@ QUnit.test( "$.widget.extend()", function( assert ) { assert.deepEqual( input, output, "don't clone arrays" ); input.key[ 0 ] = 10; assert.deepEqual( input, output, "don't clone arrays" ); + + input = Object.create( null ); + input.foo = "f"; + output = $.widget.extend( {}, input ); + assert.deepEqual( input, output, "Object with no prototype" ); } ); } ); diff --git a/ui/widget.js b/ui/widget.js index f87675712..011396811 100644 --- a/ui/widget.js +++ b/ui/widget.js @@ -26,6 +26,7 @@ }( function( $ ) { var widgetUuid = 0; +var widgetHasOwnProperty = Array.prototype.hasOwnProperty; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { @@ -183,7 +184,7 @@ $.widget.extend = function( target ) { for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { -- cgit v1.2.3 From 0d25a36eecb9e5598596208e4852b3c3fdbf5510 Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 18 Apr 2017 17:37:15 -0400 Subject: Menu: Close menus immediately on selection or click outside Fixes #15034 Closes gh-1807 --- tests/unit/menu/events.js | 4 +++- ui/widgets/menu.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'tests/unit') diff --git a/tests/unit/menu/events.js b/tests/unit/menu/events.js index a8ccb0282..90507bddc 100644 --- a/tests/unit/menu/events.js +++ b/tests/unit/menu/events.js @@ -670,7 +670,9 @@ QUnit.test( "handle keyboard navigation and mouse click on menu with dividers an element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); - assert.equal( logOutput(), "keydown,3,4,7", "Keydown focus skips divider and group label" ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + assert.equal( logOutput(), "keydown,1,2,3,4,7", "Keydown focus skips divider and group label" ); ready(); } } ); diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 49da26865..19d9b90df 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -162,7 +162,7 @@ return $.widget( "ui.menu", { this._on( this.document, { click: function( event ) { if ( this._closeOnDocumentClick( event ) ) { - this.collapseAll( event ); + this.collapseAll( event, true ); } // Reset the mouseHandled flag @@ -502,7 +502,7 @@ return $.widget( "ui.menu", { this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); this.activeMenu = currentMenu; - }, this.delay ); + }, all ? 0 : this.delay ); }, // With no arguments, closes the currently active menu - if nothing is active -- cgit v1.2.3 From adcf9b6f6ef9c6dfa88932b40307f581e65bc667 Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 2 May 2017 12:42:49 -0400 Subject: Labels: Handle empty sets Fixes #15184 --- tests/unit/core/core.js | 4 +++- ui/labels.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'tests/unit') diff --git a/tests/unit/core/core.js b/tests/unit/core/core.js index 770ea4138..b5b6b3b81 100644 --- a/tests/unit/core/core.js +++ b/tests/unit/core/core.js @@ -142,7 +142,7 @@ QUnit.test( "uniqueId / removeUniqueId", function( assert ) { } ); QUnit.test( "Labels", function( assert ) { - assert.expect( 2 ); + assert.expect( 3 ); var expected = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ]; var dom = $( "#labels-fragment" ); @@ -165,6 +165,8 @@ QUnit.test( "Labels", function( assert ) { // Detach the dom to test on a fragment dom.detach(); testLabels( "document fragments" ); + + assert.equal( $().labels().length, 0, "No element" ); } ); ( function() { diff --git a/ui/labels.js b/ui/labels.js index 2a78d886b..1bf56ac41 100644 --- a/ui/labels.js +++ b/ui/labels.js @@ -27,6 +27,10 @@ return $.fn.labels = function() { var ancestor, selector, id, labels, ancestors; + if ( !this.length ) { + return this.pushStack( [] ); + } + // Check control.labels first if ( this[ 0 ].labels && this[ 0 ].labels.length ) { return this.pushStack( this[ 0 ].labels ); -- cgit v1.2.3 From abc9e7ce2f3b60a18bf1f461c7cbfccb3fa02b53 Mon Sep 17 00:00:00 2001 From: Alexander Schmitz Date: Wed, 19 Apr 2017 11:32:13 -0400 Subject: Button: Fix backcompat when called on collection of mixed elements Fixes #15109 Closes gh-1808 --- tests/unit/button/deprecated.html | 8 ++++ tests/unit/button/deprecated.js | 18 ++++++++ ui/widgets/button.js | 87 ++++++++++++++++++++++++++++++++------- 3 files changed, 99 insertions(+), 14 deletions(-) (limited to 'tests/unit') diff --git a/tests/unit/button/deprecated.html b/tests/unit/button/deprecated.html index 73f62921c..8b5270baa 100644 --- a/tests/unit/button/deprecated.html +++ b/tests/unit/button/deprecated.html @@ -56,6 +56,14 @@ Anchor Button +
+ Anchor + + + + +
+
diff --git a/tests/unit/button/deprecated.js b/tests/unit/button/deprecated.js index 81a0281b7..86fca797e 100644 --- a/tests/unit/button/deprecated.js +++ b/tests/unit/button/deprecated.js @@ -194,4 +194,22 @@ QUnit.test( "icon / icons options properly proxied", function( assert ) { "Icons secondary option sets iconPosition option to end on init" ); } ); +QUnit.test( "Calling button on a collection of mixed types works correctly", function( assert ) { + assert.expect( 5 ); + + var group = $( ".mixed" ).children(); + + group.button(); + + $.each( { + anchor: "button", + button: "button", + check: "checkboxradio", + input: "button", + radio: "checkboxradio" + }, function( type, widget ) { + assert.ok( $( "#mixed-" + type )[ widget ]( "instance" ), type + " is a " + widget ); + } ); +} ); + } ); diff --git a/ui/widgets/button.js b/ui/widgets/button.js index 50da9f9e2..42cfec06d 100644 --- a/ui/widgets/button.js +++ b/ui/widgets/button.js @@ -342,22 +342,81 @@ if ( $.uiBackCompat !== false ) { } ); $.fn.button = ( function( orig ) { - return function() { - if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) || - ( this.length && this[ 0 ].tagName === "INPUT" && ( - this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio" - ) ) ) { - return orig.apply( this, arguments ); - } - if ( !$.ui.checkboxradio ) { - $.error( "Checkboxradio widget missing" ); - } - if ( arguments.length === 0 ) { - return this.checkboxradio( { - "icon": false + return function( options ) { + var isMethodCall = typeof options === "string"; + var args = Array.prototype.slice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? + "button" : + "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on button" + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for button" + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? "button" : "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + if ( name === "button" ) { + orig.call( $( this ), options ); + return; + } + + $( this ).checkboxradio( $.extend( { icon: false }, options ) ); + } } ); } - return this.checkboxradio.apply( this, arguments ); + + return returnValue; }; } )( $.fn.button ); -- cgit v1.2.3 From a3e953b495905d0c67790e65032841451b470ce1 Mon Sep 17 00:00:00 2001 From: Scott González Date: Mon, 17 Apr 2017 12:26:22 -0400 Subject: Menu: Don't focus dividers when wrapping via keyboard navigation Fixes #15157 Closes gh-1804 --- tests/unit/menu/events.js | 22 ++++++++++++++++++++++ tests/unit/menu/menu.html | 10 ++++++++++ ui/widgets/menu.js | 24 +++++++++++++----------- 3 files changed, 45 insertions(+), 11 deletions(-) (limited to 'tests/unit') diff --git a/tests/unit/menu/events.js b/tests/unit/menu/events.js index 90507bddc..fd57373c1 100644 --- a/tests/unit/menu/events.js +++ b/tests/unit/menu/events.js @@ -757,4 +757,26 @@ QUnit.test( "#10571: When typing in a menu, only menu-items should be focused", } ); } ); +QUnit.test( "#15157: Must not focus menu dividers with the keyboard", function( assert ) { + var ready = assert.async(); + assert.expect( 6 ); + + var element = $( "#menu-with-dividers" ).menu( { + focus: function( event, ui ) { + assert.hasClasses( ui.item, "ui-menu-item", "Should have menu item class" ); + log( ui.item.text() ); + } + } ); + + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } ); + setTimeout( function() { + assert.equal( logOutput(), "beginning,middle,end,beginning,end", "Should wrap around items" ); + ready(); + } ); +} ); + } ); diff --git a/tests/unit/menu/menu.html b/tests/unit/menu/menu.html index 2d871a4b3..8d70b19df 100644 --- a/tests/unit/menu/menu.html +++ b/tests/unit/menu/menu.html @@ -323,6 +323,16 @@
  • Addyston
  • Adelphi
  • + + diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 19d9b90df..b2d976a2b 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -136,7 +136,7 @@ return $.widget( "ui.menu", { // If there's already an active item, keep it active // If not, activate the first item - var item = this.active || this.element.find( this.options.items ).eq( 0 ); + var item = this.active || this._menuItems().first(); if ( !keepActiveItem ) { this.focus( event, item ); @@ -538,11 +538,7 @@ return $.widget( "ui.menu", { }, expand: function( event ) { - var newItem = this.active && - this.active - .children( ".ui-menu " ) - .find( this.options.items ) - .first(); + var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); @@ -570,21 +566,27 @@ return $.widget( "ui.menu", { return this.active && !this.active.nextAll( ".ui-menu-item" ).length; }, + _menuItems: function( menu ) { + return ( menu || this.element ) + .find( this.options.items ) + .filter( ".ui-menu-item" ); + }, + _move: function( direction, filter, event ) { var next; if ( this.active ) { if ( direction === "first" || direction === "last" ) { next = this.active [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) - .eq( -1 ); + .last(); } else { next = this.active [ direction + "All" ]( ".ui-menu-item" ) - .eq( 0 ); + .first(); } } if ( !next || !next.length || !this.active ) { - next = this.activeMenu.find( this.options.items )[ filter ](); + next = this._menuItems( this.activeMenu )[ filter ](); } this.focus( event, next ); @@ -610,7 +612,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ) + this.focus( event, this._menuItems( this.activeMenu ) [ !this.active ? "first" : "last" ]() ); } }, @@ -634,7 +636,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ).first() ); + this.focus( event, this._menuItems( this.activeMenu ).first() ); } }, -- cgit v1.2.3 From 50efd6e1b063822c4a0ecb38f324ed3354f387c4 Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 18 Apr 2017 16:51:23 -0400 Subject: Menu: Ignore mouse events triggered due to page scrolling Fixes #9356 Closes gh-1806 --- tests/unit/selectmenu/core.js | 4 ++-- tests/unit/selectmenu/events.js | 4 ++-- ui/widgets/menu.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'tests/unit') diff --git a/tests/unit/selectmenu/core.js b/tests/unit/selectmenu/core.js index cef6dc5bf..708452312 100644 --- a/tests/unit/selectmenu/core.js +++ b/tests/unit/selectmenu/core.js @@ -251,13 +251,13 @@ $.each( [ wrappers = menu.find( "li.ui-menu-item .ui-menu-item-wrapper" ); button.trigger( "click" ); - wrappers.first().simulate( "mouseover" ).trigger( "click" ); + wrappers.first().simulate( "mouseover", { clientX: 2, clientY: 2 } ).trigger( "click" ); assert.equal( element[ 0 ].selectedIndex, 0, "First item is selected" ); button.simulate( "keydown", { keyCode: $.ui.keyCode.UP } ); assert.equal( element[ 0 ].selectedIndex, 0, "No looping beyond first item" ); button.trigger( "click" ); - wrappers.last().simulate( "mouseover" ).trigger( "click" ); + wrappers.last().simulate( "mouseover", { clientX: 3, clientY: 3 } ).trigger( "click" ); assert.equal( element[ 0 ].selectedIndex, wrappers.length - 1, "Last item is selected" ); button.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); assert.equal( element[ 0 ].selectedIndex, wrappers.length - 1, "No looping behind last item" ); diff --git a/tests/unit/selectmenu/events.js b/tests/unit/selectmenu/events.js index 4aed70ac8..ffc0429ee 100644 --- a/tests/unit/selectmenu/events.js +++ b/tests/unit/selectmenu/events.js @@ -89,9 +89,9 @@ QUnit.test( "focus", function( assert ) { button.trigger( "click" ); links = menu.find( "li.ui-menu-item" ); optionIndex = 0; - links.eq( optionIndex ).simulate( "mouseover" ); + links.eq( optionIndex ).simulate( "mouseover", { clientX: 2, clientY: 2 } ); optionIndex += 1; - links.eq( optionIndex ).simulate( "mouseover" ); + links.eq( optionIndex ).simulate( "mouseover", { clientX: 3, clientY: 3 } ); // This tests for unwanted, additional focus event on close that.element.selectmenu( "close" ); diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 5df9d3eef..302d202ae 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -64,6 +64,7 @@ return $.widget( "ui.menu", { // Flag used to prevent firing of the click handler // as the event bubbles up through nested menus this.mouseHandled = false; + this.lastMousePosition = { x: null, y: null }; this.element .uniqueId() .attr( { @@ -161,6 +162,17 @@ return $.widget( "ui.menu", { return; } + // If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356) + if ( event.clientX === this.lastMousePosition.x && + event.clientY === this.lastMousePosition.y ) { + return; + } + + this.lastMousePosition = { + x: event.clientX, + y: event.clientY + }; + var actualTarget = $( event.target ).closest( ".ui-menu-item" ), target = $( event.currentTarget ); -- cgit v1.2.3