diff --git a/tests/unit/sortable/sortable.html b/tests/unit/sortable/sortable.html index 8e0bac501f4..87f86b7d1f6 100644 --- a/tests/unit/sortable/sortable.html +++ b/tests/unit/sortable/sortable.html @@ -16,6 +16,7 @@ "ui/jquery.ui.core.js", "ui/jquery.ui.widget.js", "ui/jquery.ui.mouse.js", + "ui/jquery.ui.draggable.js", "ui/jquery.ui.sortable.js" ] }); @@ -47,6 +48,25 @@ #sortable-table { width: 100%; } + ul.sortableoutover, ul.sortableoutoverdraggable { + position:relative; + top:0; + left:0; + } + ul.sortableoutover, ul.sortableoutover li, ul.sortableoutoverdraggable, ul.sortableoutoverdraggable li { + margin: 0; + padding: 0; + border-width: 0; + } + ul.sortableoutover li, ul.sortableoutoverdraggable li { + height: 20px; + } + #sortableoutoverspace { + height: 100px; + } + #sortableoutoverdraggablelist { + height: 20px; + } @@ -94,6 +114,30 @@

+ + +
+Space +
+ + + + + diff --git a/tests/unit/sortable/sortable_events.js b/tests/unit/sortable/sortable_events.js index 46a493b3e7d..5b41491364a 100644 --- a/tests/unit/sortable/sortable_events.js +++ b/tests/unit/sortable/sortable_events.js @@ -248,21 +248,187 @@ test( "over", function() { dy: 20 }); - ok( hash, "stop event triggered" ); + ok( hash, "over event triggered" ); + ok( hash.helper, "UI includes: helper" ); + ok( hash.placeholder, "UI hash includes: placeholder" ); + ok( hash.position && ( "top" in hash.position && "left" in hash.position ), "UI hash includes: position" ); + ok( hash.offset && ( hash.offset.top && hash.offset.left ), "UI hash includes: offset" ); + ok( hash.item, "UI hash includes: item" ); + ok( hash.sender, "UI hash includes: sender" ); + equal( overCount, 1, "over fires only once" ); +}); + +test( "out", function() { + expect( 8 ); + + var hash, + outCount = 0; + + $("#sortableoutover").sortable({ + out: function( e, ui ) { + hash = ui; + outCount++; + } + }).find( "li:eq(0)" ).simulate( "drag", { + dy: 150 + }); + + ok( hash, "out event triggered" ); ok( hash.helper, "UI should not include: helper" ); ok( hash.placeholder, "UI hash includes: placeholder" ); ok( hash.position && ( "top" in hash.position && "left" in hash.position ), "UI hash includes: position" ); ok( hash.offset && ( hash.offset.top && hash.offset.left ), "UI hash includes: offset" ); ok( hash.item, "UI hash includes: item" ); ok( hash.sender, "UI hash does not include: sender" ); - equal( overCount, 1, "over fires only once" ); + equal( outCount, 1, "out fires only once" ); }); -/* -test("out", function() { - ok(false, "missing test - untested code is broken code."); +test("#9335: over and out firing, including with connectWith and a draggable", function() { + expect(24); + + /* + * The fixture structure is: + * - a UL with 5 * LI of 20px height each (total height 100px) to be made sortable + * - a DIV of height 100px + * - a UL with 1 * LI of 20px height (total height fixed at 20px) with the LI to be made draggable + * - a UL with 5 * LI of 20px height each (total height 100px) to be made sortable + */ + + var fired = {}, + firedCount = function (id,type) { var key=id + "-" + type; return (key in fired) ? fired[key] : 0; }, + fire = function (sortable,type) { var key=$(sortable).attr("id") + "-" + type; fired[key] = ((key in fired) ? fired[key] : 0) + 1; }, + sortable_ul=$("#sortableoutover"), + drag_el = sortable_ul.find( "li:eq(0)" ), + draggable_container=$("#sortableoutoverdraggablelist"), + draggable=draggable_container.find("li:eq(0)"); + + // Initialise with hooks to count how often out and over events fire + $("#qunit-fixture ul.sortableoutover").sortable({ + connectWith: "#qunit-fixture ul.sortableoutover", + out: function () { + fire(this,"out"); + }, + over: function () { + fire(this,"over"); + } + }); + draggable.draggable({ + connectToSortable: "#qunit-fixture ul.sortableoutover", + revert:"invalid" + }); + + // Test that after dragging out (but keeping the mouse down) + // registers an initial over and then an out against the main sortable + fired = {}; + TestHelpers.sortable.dragBegin(drag_el, { + dy: 150 + }); + equal( firedCount( "sortableoutover", "over" ), 1, "Drag outside sortable fires over once initially" ); + equal( firedCount( "sortableoutover", "out" ), 1, "Drag outside sortable fires out once" ); + + // Release the mouse button while not over the sortable triggers no 'out' event + fired = {}; + TestHelpers.sortable.dragEnd(drag_el, { }); + equal( firedCount( "sortableoutover", "over" ), 0, "Completion of drag outside sortable fires no over" ); + equal( firedCount( "sortableoutover", "out" ), 0, "Completion of drag outside sortable fires no out" ); + + // NB: because the above drag may well have resulted in the drag element being repositioned in the + // list we get the current first element again + drag_el = sortable_ul.find( "li:eq(0)" ); + + // Test that dragging out and then back over fires an initial over, the out, and then another over + fired = {}; + TestHelpers.sortable.dragBegin(drag_el, { + dy: 150 + }); + TestHelpers.sortable.dragContinue(drag_el, { + dy: -150 + }); + equal( firedCount( "sortableoutover", "over" ), 2, "Drag outside and then back over sortable fires over once initially and then a second on return" ); + equal( firedCount( "sortableoutover", "out" ), 1, "Drag outside and then back over sortable fires out once" ); + + // Releasing the mouse button while 'over' triggers an 'out' + fired = {}; + TestHelpers.sortable.dragEnd(drag_el, { }); + equal( firedCount( "sortableoutover", "over" ), 0, "Releasing the mouse button after a drag out and then back over triggers no further over" ); + equal( firedCount( "sortableoutover", "out" ), 1, "Releasing the mouse button after a drag out and then back over triggers a final out" ); + + // Test that dragging out and then over second sortable fires initial over and out on the first + // and then an over on the second + fired = {}; + TestHelpers.sortable.dragBegin(drag_el, { + dy: 150 + }); + TestHelpers.sortable.dragContinue(drag_el, { + dy: 100 + }); + equal( firedCount( "sortableoutover", "over" ), 1, "Drag outside first and then over second sortable fires over on first sortable" ); + equal( firedCount( "sortableoutover", "out" ), 1, "Drag outside first and then over second sortable fires out on first sortable" ); + equal( firedCount( "sortableoutover2", "over" ), 1, "Drag outside first and then over second sortable fires over on second sortable" ); + equal( firedCount( "sortableoutover2", "out" ), 0, "Drag outside first and then over second sortable fires no out on second sortable" ); + + // Releasing the mouse button while 'over' triggers an 'out' - this time it should be on the second sortable, not the first + fired = {}; + TestHelpers.sortable.dragEnd(drag_el, { }); + equal( firedCount( "sortableoutover", "out" ), 0, "Releasing the mouse button after a drag out and over the second sortable shouldn't trigger an out on the first" ); + equal( firedCount( "sortableoutover2", "out" ), 1, "Releasing the mouse button after a drag out and over the second sortable should trigger an out on the second" ); + + // Dragging draggable over one sorted list and then out and then over another sorted list and then out + // should trigger one over and one out on each + fired = {}; + TestHelpers.sortable.dragBegin(draggable, { + dy: 70 // Over second sortable + }); + TestHelpers.sortable.dragContinue(draggable, { + dy: -120 // Over space div + }); + TestHelpers.sortable.dragContinue(draggable, { + dy: -100 // Over first sortable + }); + TestHelpers.sortable.dragContinue(draggable, { + dy: 100 // Over space div + }); + equal( firedCount( "sortableoutover", "over" ), 1, "Dragging over both sortables and then back out should trigger over on first" ); + equal( firedCount( "sortableoutover", "out" ), 1, "Dragging over both sortables and then back out should trigger out on first" ); + equal( firedCount( "sortableoutover2", "over" ), 1, "Dragging over both sortables and then back out should trigger over on second" ); + equal( firedCount( "sortableoutover2", "out" ), 1, "Dragging over both sortables and then back out should trigger out on second" ); + + // Release the mouse button while outside both sortables shouldn't trigger any further out events + fired = {}; + TestHelpers.sortable.dragEnd(draggable, { }); + equal( firedCount( "sortableoutover", "out" ), 0, "Dragging over both sortables and then back out shouldn't trigger any further out event on first on mouseup" ); + equal( firedCount( "sortableoutover2", "out" ), 0, "Dragging over both sortables and then back out shouldn't trigger any further out event on second on mouseup" ); + + // At the time of writing this test if you drop a draggable outside of a sortable but it was + // over a sortable at some point during the drag then it ends up in the sortable (i.e. it doesn't revert), so reset + // the draggable back to being in its container before the next test + draggable + .draggable("destroy") + .appendTo(draggable_container) + .draggable({ + connectToSortable: "#qunit-fixture ul.sortableoutover", + revert:"invalid" + }); + + // Test dragging draggable over connected sortable + // registers an initial over and then an out against the main sortable + fired = {}; + TestHelpers.sortable.dragBegin(draggable, { + dy: 50 + }); + equal( firedCount( "sortableoutover2", "over" ), 1, "Dragging draggable over sortable should trigger over" ); + equal( firedCount( "sortableoutover2", "out" ), 0, "Dragging draggable over sortable shouldn't trigger out until mouseup" ); + + // Release the mouse button while over the sortable should trigger out + // NB: this will have dropped drag_el on to this second list + fired = {}; + TestHelpers.sortable.dragEnd(draggable, { }); + equal( firedCount( "sortableoutover2", "over" ), 0, "Completion of drag of draggable over sortable shouldn't trigger over" ); + equal( firedCount( "sortableoutover2", "out" ), 1, "Completion of drag of draggable over sortable should trigger out" ); + }); +/* test("activate", function() { ok(false, "missing test - untested code is broken code."); }); diff --git a/tests/unit/sortable/sortable_test_helpers.js b/tests/unit/sortable/sortable_test_helpers.js index 12e4829ea2c..aecbdbccbb6 100644 --- a/tests/unit/sortable/sortable_test_helpers.js +++ b/tests/unit/sortable/sortable_test_helpers.js @@ -5,5 +5,66 @@ TestHelpers.sortable = { dy: dy }); equal( $( handle ).parent().children().index( handle ), index, msg ); + }, + findCenter: function ( elem ) { + var offset, + document = $( elem[0].ownerDocument ); + offset = elem.offset(); + + return { + x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(), + y: offset.top + elem.outerHeight() / 2 - document.scrollTop() + }; + }, + drag: function( target, options ) { + // Adapted from the jquery simulate plugin - a version that can avoid doing mouseup or mouse down is useful + // - don't currently need all the functionality from there, but it seemed pointless to pare it down + // in case it does become useful later. + + target = $( target ); + var i = 0, + center = TestHelpers.sortable.findCenter( target ), + x = Math.floor( center.x ), + y = Math.floor( center.y ), + coord = { clientX: x, clientY: y }, + dx = options.dx || ( options.x !== undefined ? options.x - x : 0 ), + dy = options.dy || ( options.y !== undefined ? options.y - y : 0 ), + moves = options.moves || 3, + nomousedown = options.nomousedown || false, + nomouseup = options.nomouseup || false; + + if ( !nomousedown ) { + target.simulate( "mousedown", coord ); + } + + for ( ; i < moves ; i++ ) { + x += dx / moves; + y += dy / moves; + + coord = { + clientX: Math.round( x ), + clientY: Math.round( y ) + }; + + $( target[0].ownerDocument ).simulate( "mousemove", coord ); + } + + if ( !nomouseup ) { + if ( $.contains( document, target[0] ) ) { + target.simulate( "mouseup", coord ); + target.simulate( "click", coord ); + } else { + $( document ).simulate( "mouseup", coord ); + } + } + }, + dragBegin: function ( target, options ) { + TestHelpers.sortable.drag( target, $.extend( {} , options, { "nomouseup":true } ) ); + }, + dragContinue: function ( target, options ) { + TestHelpers.sortable.drag( target, $.extend( {} , options, { "nomouseup":true, "nomousedown":true } ) ); + }, + dragEnd: function ( target, options ) { + TestHelpers.sortable.drag( target, $.extend( {} , options, { "nomousedown":true } ) ); } }; \ No newline at end of file diff --git a/ui/jquery.ui.sortable.js b/ui/jquery.ui.sortable.js index 9c7bf446cda..0a24d8d238a 100644 --- a/ui/jquery.ui.sortable.js +++ b/ui/jquery.ui.sortable.js @@ -878,20 +878,21 @@ $.widget("ui.sortable", $.ui.mouse, { return; } - if(this.currentContainer === this.containers[innermostIndex]) { - return; - } - - itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); - this._trigger("change", event, this._uiHash()); - this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); - this.currentContainer = this.containers[innermostIndex]; + if(this.currentContainer !== this.containers[innermostIndex]) { + itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); + this._trigger("change", event, this._uiHash()); + this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); + this.currentContainer = this.containers[innermostIndex]; - //Update the placeholder - this.options.placeholder.update(this.currentContainer, this.placeholder); + //Update the placeholder + this.options.placeholder.update(this.currentContainer, this.placeholder); + } + - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; + if (!this.containers[innermostIndex].containerCache.over) { + this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); + this.containers[innermostIndex].containerCache.over = 1; + } }