mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-11-03 05:39:45 +00:00
AppKit: Fix outline drop retargeting and add interaction regression tests
Protects the selection, editing and drag and drop contracts of tables and outlines by verifying the events, delegate calls and data source calls that applications depend on. A drop retargeted between the children of an item is delivered to that item. * CPOutlineView: Set the retargeted drop operation in setDropItem:dropChildIndex:. * CPTableViewInteractionTest: Verify selection by mouse and keyboard, editing, drag initiation and drop handling, including refusal and retargeting by the delegate and data source. * CPOutlineViewInteractionTest: Verify dragged items, proposed parents and child indexes, drop retargeting, and expansion and its cancellation during a hover.
This commit is contained in:
1 parent
f468d483ff
commit
fca3a9159a
3 files changed
+1136
-1
No files matched your search
@@ -1581,7 +1581,9 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
|
||||
_retargedChildIndex = theIndex;
|
||||
_shouldRetargetChildIndex = YES;
|
||||
|
||||
// set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
|
||||
// set CPTableView's _retargetedDropOperation and _retargetedDropRow based on retargetedItem and retargetedChildIndex
|
||||
_retargetedDropOperation = (_retargedChildIndex === CPOutlineViewDropOnItemIndex) ? CPTableViewDropOn : CPTableViewDropAbove;
|
||||
|
||||
var retargetedItemInfo = (_retargetedItem != nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
|
||||
|
||||
if (_retargedChildIndex === [retargetedItemInfo.children count])
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* CPOutlineViewInteractionTest.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by David Richardson on 31/10/2026.
|
||||
* Copyright David Richardson 2026.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
/*
|
||||
An outline view adds a parent to every row, so it reports a drag by item and a drop
|
||||
by parent item and child index. Selection and editing come from CPTableView, and
|
||||
CPTableViewInteractionTest.j covers them. These tests cover what an outline view
|
||||
changes.
|
||||
*/
|
||||
|
||||
var OutlineInteractionDragType = @"OutlineInteractionDragType";
|
||||
|
||||
@implementation _CPOutlineInteractionView : CPOutlineView
|
||||
{
|
||||
CPArray dragCalls @accessors;
|
||||
}
|
||||
|
||||
- (void)dragView:(CPView)aView at:(CGPoint)aLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
if (!dragCalls)
|
||||
dragCalls = [];
|
||||
|
||||
dragCalls.push(aPasteboard);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPOutlineInteractionDataSource : CPObject
|
||||
{
|
||||
id tree @accessors;
|
||||
CPArray writtenItems @accessors;
|
||||
CPArray validations @accessors;
|
||||
CPArray acceptedDrops @accessors;
|
||||
id retargetItem @accessors;
|
||||
id retargetIndex @accessors;
|
||||
BOOL shouldRetarget @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
tree = {
|
||||
@"root": [@"A", @"B", @"C"],
|
||||
@"A": [@"A1", @"A2"],
|
||||
@"B": [],
|
||||
@"C": [@"C1"]
|
||||
};
|
||||
validations = [];
|
||||
acceptedDrops = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPArray)childrenOfItem:(id)anItem
|
||||
{
|
||||
return tree[anItem || @"root"] || [];
|
||||
}
|
||||
|
||||
- (id)outlineView:(CPOutlineView)anOutlineView child:(int)anIndex ofItem:(id)anItem
|
||||
{
|
||||
return [self childrenOfItem:anItem][anIndex];
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(CPOutlineView)anOutlineView isItemExpandable:(id)anItem
|
||||
{
|
||||
return [self childrenOfItem:anItem].length > 0;
|
||||
}
|
||||
|
||||
- (int)outlineView:(CPOutlineView)anOutlineView numberOfChildrenOfItem:(id)anItem
|
||||
{
|
||||
return [self childrenOfItem:anItem].length;
|
||||
}
|
||||
|
||||
- (id)outlineView:(CPOutlineView)anOutlineView objectValueForTableColumn:(CPTableColumn)aColumn byItem:(id)anItem
|
||||
{
|
||||
return anItem;
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(CPOutlineView)anOutlineView writeItems:(CPArray)theItems toPasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
writtenItems = theItems;
|
||||
|
||||
[aPasteboard declareTypes:[OutlineInteractionDragType] owner:self];
|
||||
[aPasteboard setData:@"items" forType:OutlineInteractionDragType];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPDragOperation)outlineView:(CPOutlineView)anOutlineView validateDrop:(id)anInfo proposedItem:(id)anItem proposedChildIndex:(int)anIndex
|
||||
{
|
||||
validations.push({ @"item": anItem, @"index": anIndex });
|
||||
|
||||
if (shouldRetarget)
|
||||
[anOutlineView setDropItem:retargetItem dropChildIndex:retargetIndex];
|
||||
|
||||
return CPDragOperationMove;
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(CPOutlineView)anOutlineView acceptDrop:(id)anInfo item:(id)anItem childIndex:(int)anIndex
|
||||
{
|
||||
acceptedDrops.push({ @"item": anItem, @"index": anIndex });
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPOutlineInteractionDraggingInfo : CPObject
|
||||
{
|
||||
CPPasteboard draggingPasteboard @accessors;
|
||||
CGPoint draggingLocation @accessors;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPOutlineViewInteractionTest : OJTestCase
|
||||
{
|
||||
CPWindow theWindow;
|
||||
_CPOutlineInteractionView outlineView;
|
||||
_CPOutlineInteractionDataSource dataSource;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// This will init the global var CPApp which are used internally in the AppKit
|
||||
[[CPApplication alloc] init];
|
||||
|
||||
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 400.0, 400.0) styleMask:CPWindowNotSizable];
|
||||
outlineView = [[_CPOutlineInteractionView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
|
||||
|
||||
var column = [[CPTableColumn alloc] initWithIdentifier:@"Column"];
|
||||
|
||||
[column setWidth:200.0];
|
||||
[outlineView addTableColumn:column];
|
||||
[outlineView setOutlineTableColumn:column];
|
||||
[outlineView setAllowsMultipleSelection:YES];
|
||||
[[theWindow contentView] addSubview:outlineView];
|
||||
|
||||
dataSource = [[_CPOutlineInteractionDataSource alloc] init];
|
||||
|
||||
[outlineView setDataSource:dataSource];
|
||||
[outlineView registerForDraggedTypes:[OutlineInteractionDragType]];
|
||||
[outlineView expandItem:@"A"];
|
||||
[outlineView reloadData];
|
||||
}
|
||||
|
||||
- (CGPoint)pointInItem:(id)anItem
|
||||
{
|
||||
return CGPointMake(100.0, CGRectGetMidY([outlineView rectOfRow:[outlineView rowForItem:anItem]]));
|
||||
}
|
||||
|
||||
- (id)draggingInfoAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var info = [[_CPOutlineInteractionDraggingInfo alloc] init],
|
||||
pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
|
||||
|
||||
[pasteboard declareTypes:[OutlineInteractionDragType] owner:self];
|
||||
[info setDraggingPasteboard:pasteboard];
|
||||
[info setDraggingLocation:[outlineView convertPoint:aPoint toView:nil]];
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
- (void)sendMouse:(CPEventType)aType atPoint:(CGPoint)aPoint
|
||||
{
|
||||
var event = [CPEvent mouseEventWithType:aType
|
||||
location:[outlineView convertPoint:aPoint toView:nil]
|
||||
modifierFlags:0
|
||||
timestamp:0
|
||||
windowNumber:[theWindow windowNumber]
|
||||
context:nil
|
||||
eventNumber:0
|
||||
clickCount:1
|
||||
pressure:0];
|
||||
|
||||
[CPApp sendEvent:event];
|
||||
[outlineView trackMouse:event];
|
||||
}
|
||||
|
||||
- (void)testDraggingARowWritesItsItem
|
||||
{
|
||||
var start = [self pointInItem:@"A1"];
|
||||
|
||||
[self sendMouse:CPLeftMouseDown atPoint:start];
|
||||
[self sendMouse:CPLeftMouseDragged atPoint:CGPointMake(start.x + 20.0, start.y)];
|
||||
|
||||
[self assert:[@"A1"] equals:[dataSource writtenItems]];
|
||||
[self assert:1 equals:[[outlineView dragCalls] count]];
|
||||
}
|
||||
|
||||
- (void)testDraggingASelectionWritesEachItem
|
||||
{
|
||||
var firstRow = [outlineView rowForItem:@"A1"];
|
||||
|
||||
[outlineView selectRowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(firstRow, 2)] byExtendingSelection:NO];
|
||||
|
||||
var start = [self pointInItem:@"A2"];
|
||||
|
||||
[self sendMouse:CPLeftMouseDown atPoint:start];
|
||||
[self sendMouse:CPLeftMouseDragged atPoint:CGPointMake(start.x + 20.0, start.y)];
|
||||
|
||||
[self assert:[@"A1", @"A2"] equals:[dataSource writtenItems]];
|
||||
}
|
||||
|
||||
- (void)testTheMiddleOfARowProposesADropOnThatItem
|
||||
{
|
||||
[outlineView draggingUpdated:[self draggingInfoAtPoint:[self pointInItem:@"B"]]];
|
||||
|
||||
[self assert:@"B" equals:[dataSource validations][0].item];
|
||||
[self assert:CPOutlineViewDropOnItemIndex equals:[dataSource validations][0].index];
|
||||
}
|
||||
|
||||
- (void)testTheTopOfAChildRowProposesADropInItsParent
|
||||
{
|
||||
var rect = [outlineView rectOfRow:[outlineView rowForItem:@"A2"]];
|
||||
|
||||
[outlineView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(100.0, CGRectGetMinY(rect) + 1.0)]];
|
||||
|
||||
[self assert:@"A" equals:[dataSource validations][0].item];
|
||||
[self assert:1 equals:[dataSource validations][0].index];
|
||||
}
|
||||
|
||||
/*
|
||||
Between a child and a root row, either parent is possible, and the horizontal
|
||||
position of the drag chooses between them.
|
||||
*/
|
||||
- (void)testAnUnindentedDropAboveARootRowProposesTheRoot
|
||||
{
|
||||
var rect = [outlineView rectOfRow:[outlineView rowForItem:@"B"]];
|
||||
|
||||
[outlineView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(5.0, CGRectGetMinY(rect) + 1.0)]];
|
||||
|
||||
[self assertNull:[dataSource validations][0].item];
|
||||
[self assert:1 equals:[dataSource validations][0].index];
|
||||
}
|
||||
|
||||
- (void)testAnIndentedDropAboveARootRowProposesTheEndOfThePreviousParent
|
||||
{
|
||||
var rect = [outlineView rectOfRow:[outlineView rowForItem:@"B"]];
|
||||
|
||||
[outlineView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(100.0, CGRectGetMinY(rect) + 1.0)]];
|
||||
|
||||
[self assert:@"A" equals:[dataSource validations][0].item];
|
||||
[self assert:2 equals:[dataSource validations][0].index];
|
||||
}
|
||||
|
||||
- (void)testBelowTheLastRowProposesADropAtTheEndOfTheRoot
|
||||
{
|
||||
[outlineView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(100.0, 290.0)]];
|
||||
|
||||
[self assertNull:[dataSource validations][0].item];
|
||||
[self assert:3 equals:[dataSource validations][0].index];
|
||||
}
|
||||
|
||||
- (void)testAcceptingADropReceivesTheProposedItem
|
||||
{
|
||||
var info = [self draggingInfoAtPoint:[self pointInItem:@"B"]];
|
||||
|
||||
[outlineView draggingUpdated:info];
|
||||
|
||||
[self assertTrue:[outlineView performDragOperation:info]];
|
||||
[self assert:@"B" equals:[dataSource acceptedDrops][0].item];
|
||||
[self assert:CPOutlineViewDropOnItemIndex equals:[dataSource acceptedDrops][0].index];
|
||||
}
|
||||
|
||||
- (void)testDataSourceCanRetargetADropBetweenChildren
|
||||
{
|
||||
[dataSource setShouldRetarget:YES];
|
||||
[dataSource setRetargetItem:@"A"];
|
||||
[dataSource setRetargetIndex:0];
|
||||
|
||||
var info = [self draggingInfoAtPoint:[self pointInItem:@"B"]];
|
||||
|
||||
[outlineView draggingUpdated:info];
|
||||
[outlineView performDragOperation:info];
|
||||
|
||||
[self assert:@"A" equals:[dataSource acceptedDrops][0].item];
|
||||
[self assert:0 equals:[dataSource acceptedDrops][0].index];
|
||||
}
|
||||
|
||||
- (void)testDataSourceCanRetargetADropOnAnItem
|
||||
{
|
||||
[dataSource setShouldRetarget:YES];
|
||||
[dataSource setRetargetItem:@"C"];
|
||||
[dataSource setRetargetIndex:CPOutlineViewDropOnItemIndex];
|
||||
|
||||
var info = [self draggingInfoAtPoint:[self pointInItem:@"B"]];
|
||||
|
||||
[outlineView draggingUpdated:info];
|
||||
[outlineView performDragOperation:info];
|
||||
|
||||
[self assert:@"C" equals:[dataSource acceptedDrops][0].item];
|
||||
[self assert:CPOutlineViewDropOnItemIndex equals:[dataSource acceptedDrops][0].index];
|
||||
}
|
||||
|
||||
- (void)testHoveringOverACollapsedItemExpandsIt
|
||||
{
|
||||
[self assertFalse:[outlineView isItemExpanded:@"C"]];
|
||||
|
||||
[outlineView setDropItem:@"C" dropChildIndex:CPOutlineViewDropOnItemIndex];
|
||||
[outlineView._dragHoverTimer fire];
|
||||
|
||||
[self assertTrue:[outlineView isItemExpanded:@"C"]];
|
||||
}
|
||||
|
||||
- (void)testAChildIndexCancelsTheHover
|
||||
{
|
||||
[outlineView setDropItem:@"C" dropChildIndex:CPOutlineViewDropOnItemIndex];
|
||||
[outlineView setDropItem:@"C" dropChildIndex:0];
|
||||
|
||||
[self assertNull:outlineView._dragHoverTimer];
|
||||
}
|
||||
|
||||
- (void)testEndingADragCancelsTheHover
|
||||
{
|
||||
[outlineView setDropItem:@"C" dropChildIndex:CPOutlineViewDropOnItemIndex];
|
||||
[outlineView draggedView:nil endedAt:CGPointMakeZero() operation:CPDragOperationNone];
|
||||
|
||||
[self assertNull:outlineView._dragHoverTimer];
|
||||
[self assertFalse:[outlineView isItemExpanded:@"C"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
* CPTableViewInteractionTest.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by David Richardson on 31/10/2026.
|
||||
* Copyright David Richardson 2026.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
/*
|
||||
A person selects, edits and drags rows with the mouse and the keyboard. These tests
|
||||
send a table the same events, and check what its delegate and data source receive.
|
||||
|
||||
The drag session itself needs a display, so the table subclass records the call
|
||||
that would start it. A stub stands in for the dragging information. These tests
|
||||
cannot show that a drag image follows the pointer or that a drop indicator is
|
||||
visible.
|
||||
*/
|
||||
|
||||
var InteractionDragType = @"InteractionDragType";
|
||||
|
||||
@implementation _CPTableInteractionTable : CPTableView
|
||||
{
|
||||
CPArray dragCalls @accessors;
|
||||
}
|
||||
|
||||
- (void)dragView:(CPView)aView at:(CGPoint)aLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
if (!dragCalls)
|
||||
dragCalls = [];
|
||||
|
||||
dragCalls.push({ @"pasteboard": aPasteboard, @"source": aSourceObject });
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPTableInteractionDataSource : CPObject
|
||||
{
|
||||
CPArray rows @accessors;
|
||||
CPArray editedValues @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
rows = [@"Zero", @"One", @"Two", @"Three", @"Four"];
|
||||
editedValues = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)numberOfRowsInTableView:(CPTableView)aTableView
|
||||
{
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
||||
{
|
||||
return rows[aRow];
|
||||
}
|
||||
|
||||
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
||||
{
|
||||
editedValues.push({ @"row": aRow, @"value": aValue });
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPTableInteractionDragDataSource : _CPTableInteractionDataSource
|
||||
{
|
||||
BOOL allowsDrag @accessors;
|
||||
CPIndexSet writtenRows @accessors;
|
||||
CPDragOperation validation @accessors;
|
||||
BOOL acceptsDrop @accessors;
|
||||
CPArray validations @accessors;
|
||||
CPArray acceptedDrops @accessors;
|
||||
id retargetRow @accessors;
|
||||
id retargetOperation @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
allowsDrag = YES;
|
||||
validation = CPDragOperationMove;
|
||||
acceptsDrop = YES;
|
||||
validations = [];
|
||||
acceptedDrops = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
writtenRows = theIndexes;
|
||||
|
||||
if (allowsDrag)
|
||||
{
|
||||
[aPasteboard declareTypes:[InteractionDragType] owner:self];
|
||||
[aPasteboard setData:@"rows" forType:InteractionDragType];
|
||||
}
|
||||
|
||||
return allowsDrag;
|
||||
}
|
||||
|
||||
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id)anInfo proposedRow:(int)aRow proposedDropOperation:(CPTableViewDropOperation)anOperation
|
||||
{
|
||||
validations.push({ @"row": aRow, @"operation": anOperation });
|
||||
|
||||
if (retargetRow != nil)
|
||||
[aTableView setDropRow:retargetRow dropOperation:retargetOperation];
|
||||
|
||||
return validation;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id)anInfo row:(int)aRow dropOperation:(CPTableViewDropOperation)anOperation
|
||||
{
|
||||
acceptedDrops.push({ @"row": aRow, @"operation": anOperation });
|
||||
|
||||
return acceptsDrop;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPTableInteractionDelegate : CPObject
|
||||
{
|
||||
CPArray calls @accessors;
|
||||
BOOL allowsSelectionChange @accessors;
|
||||
BOOL allowsEditing @accessors;
|
||||
CPArray refusedRows @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
calls = [];
|
||||
allowsSelectionChange = YES;
|
||||
allowsEditing = YES;
|
||||
refusedRows = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView
|
||||
{
|
||||
calls.push(@"selectionShouldChange");
|
||||
|
||||
return allowsSelectionChange;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)aRow
|
||||
{
|
||||
return refusedRows.indexOf(aRow) === -1;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
||||
{
|
||||
calls.push(@"shouldEdit");
|
||||
|
||||
return allowsEditing;
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionIsChanging:(CPNotification)aNotification
|
||||
{
|
||||
calls.push(@"selectionIsChanging");
|
||||
}
|
||||
|
||||
- (void)tableViewSelectionDidChange:(CPNotification)aNotification
|
||||
{
|
||||
calls.push(@"selectionDidChange");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPTableInteractionProposalDelegate : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
- (CPIndexSet)tableView:(CPTableView)aTableView selectionIndexesForProposedSelection:(CPIndexSet)theIndexes
|
||||
{
|
||||
return [CPIndexSet indexSetWithIndex:3];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPTableInteractionDraggingInfo : CPObject
|
||||
{
|
||||
CPPasteboard draggingPasteboard @accessors;
|
||||
CGPoint draggingLocation @accessors;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTableViewInteractionTest : OJTestCase
|
||||
{
|
||||
CPWindow theWindow;
|
||||
_CPTableInteractionTable tableView;
|
||||
CPTableColumn tableColumn;
|
||||
_CPTableInteractionDataSource dataSource;
|
||||
_CPTableInteractionDelegate delegate;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// This will init the global var CPApp which are used internally in the AppKit
|
||||
[[CPApplication alloc] init];
|
||||
|
||||
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 400.0, 400.0) styleMask:CPWindowNotSizable];
|
||||
tableView = [[_CPTableInteractionTable alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
|
||||
tableColumn = [[CPTableColumn alloc] initWithIdentifier:@"Column"];
|
||||
|
||||
[tableColumn setWidth:200.0];
|
||||
[tableColumn setEditable:YES];
|
||||
[tableView addTableColumn:tableColumn];
|
||||
[tableView setAllowsMultipleSelection:YES];
|
||||
[[theWindow contentView] addSubview:tableView];
|
||||
|
||||
delegate = [[_CPTableInteractionDelegate alloc] init];
|
||||
[self useDataSource:[[_CPTableInteractionDataSource alloc] init]];
|
||||
[tableView setDelegate:delegate];
|
||||
}
|
||||
|
||||
- (void)useDataSource:(id)aDataSource
|
||||
{
|
||||
dataSource = aDataSource;
|
||||
|
||||
[tableView setDataSource:dataSource];
|
||||
[tableView reloadData];
|
||||
}
|
||||
|
||||
- (CGPoint)pointInRow:(int)aRow
|
||||
{
|
||||
return CGPointMake(20.0, CGRectGetMidY([tableView rectOfRow:aRow]));
|
||||
}
|
||||
|
||||
- (CPEvent)mouseEvent:(CPEventType)aType atPoint:(CGPoint)aPoint flags:(unsigned)theFlags clicks:(int)aClickCount
|
||||
{
|
||||
return [CPEvent mouseEventWithType:aType
|
||||
location:[tableView convertPoint:aPoint toView:nil]
|
||||
modifierFlags:theFlags
|
||||
timestamp:0
|
||||
windowNumber:[theWindow windowNumber]
|
||||
context:nil
|
||||
eventNumber:0
|
||||
clickCount:aClickCount
|
||||
pressure:0];
|
||||
}
|
||||
|
||||
- (void)sendEvent:(CPEvent)anEvent
|
||||
{
|
||||
[CPApp sendEvent:anEvent];
|
||||
[tableView trackMouse:anEvent];
|
||||
}
|
||||
|
||||
- (void)clickPoint:(CGPoint)aPoint flags:(unsigned)theFlags clicks:(int)aClickCount
|
||||
{
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDown atPoint:aPoint flags:theFlags clicks:aClickCount]];
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseUp atPoint:aPoint flags:theFlags clicks:aClickCount]];
|
||||
}
|
||||
|
||||
- (void)clickRow:(int)aRow
|
||||
{
|
||||
[self clickPoint:[self pointInRow:aRow] flags:0 clicks:1];
|
||||
}
|
||||
|
||||
- (void)clickRow:(int)aRow flags:(unsigned)theFlags
|
||||
{
|
||||
[self clickPoint:[self pointInRow:aRow] flags:theFlags clicks:1];
|
||||
}
|
||||
|
||||
- (void)pressKey:(CPString)aCharacter flags:(unsigned)theFlags
|
||||
{
|
||||
[tableView keyDown:[CPEvent keyEventWithType:CPKeyDown
|
||||
location:CGPointMakeZero()
|
||||
modifierFlags:theFlags
|
||||
timestamp:0
|
||||
windowNumber:[theWindow windowNumber]
|
||||
context:nil
|
||||
characters:aCharacter
|
||||
charactersIgnoringModifiers:aCharacter
|
||||
isARepeat:NO
|
||||
keyCode:0]];
|
||||
}
|
||||
|
||||
- (CPArray)selectedRows
|
||||
{
|
||||
var rows = [];
|
||||
|
||||
[[tableView selectedRowIndexes] getIndexes:rows maxCount:-1 inIndexRange:nil];
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
- (id)draggingInfoAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var info = [[_CPTableInteractionDraggingInfo alloc] init],
|
||||
pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
|
||||
|
||||
[pasteboard declareTypes:[InteractionDragType] owner:self];
|
||||
[info setDraggingPasteboard:pasteboard];
|
||||
[info setDraggingLocation:[tableView convertPoint:aPoint toView:nil]];
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
- (void)useDragDataSource
|
||||
{
|
||||
[self useDataSource:[[_CPTableInteractionDragDataSource alloc] init]];
|
||||
[tableView registerForDraggedTypes:[InteractionDragType]];
|
||||
}
|
||||
|
||||
// MARK: Selection
|
||||
|
||||
- (void)testClickSelectsARow
|
||||
{
|
||||
[self clickRow:2];
|
||||
|
||||
[self assert:[2] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testClickReplacesTheSelection
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self clickRow:3];
|
||||
|
||||
[self assert:[3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testClickOutsideTheRowsClearsTheSelection
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self clickPoint:CGPointMake(20.0, 290.0) flags:0 clicks:1];
|
||||
|
||||
[self assert:[] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testClickOutsideTheRowsKeepsTheSelectionWhenItCannotBeEmpty
|
||||
{
|
||||
[tableView setAllowsEmptySelection:NO];
|
||||
[self clickRow:1];
|
||||
[self clickPoint:CGPointMake(20.0, 290.0) flags:0 clicks:1];
|
||||
|
||||
[self assert:[1] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testCommandClickAddsAndRemovesARow
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self clickRow:3 flags:CPCommandKeyMask];
|
||||
|
||||
[self assert:[1, 3] equals:[self selectedRows]];
|
||||
|
||||
[self clickRow:1 flags:CPCommandKeyMask];
|
||||
|
||||
[self assert:[3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testCommandClickReplacesTheSelectionWithoutMultipleSelection
|
||||
{
|
||||
[tableView setAllowsMultipleSelection:NO];
|
||||
[self clickRow:1];
|
||||
[self clickRow:3 flags:CPCommandKeyMask];
|
||||
|
||||
[self assert:[3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testShiftClickExtendsFromTheAnchor
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self clickRow:3 flags:CPShiftKeyMask];
|
||||
|
||||
[self assert:[1, 2, 3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testDraggingAcrossRowsExtendsTheSelection
|
||||
{
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDown atPoint:[self pointInRow:1] flags:0 clicks:1]];
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDragged atPoint:[self pointInRow:2] flags:0 clicks:1]];
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDragged atPoint:[self pointInRow:3] flags:0 clicks:1]];
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseUp atPoint:[self pointInRow:3] flags:0 clicks:1]];
|
||||
|
||||
[self assert:[1, 2, 3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testDelegateCanRefuseASelectionChange
|
||||
{
|
||||
[delegate setAllowsSelectionChange:NO];
|
||||
[self clickRow:2];
|
||||
|
||||
[self assert:[] equals:[self selectedRows]];
|
||||
[self assertTrue:[delegate calls].indexOf(@"selectionShouldChange") !== -1];
|
||||
}
|
||||
|
||||
- (void)testDelegateCanRefuseARow
|
||||
{
|
||||
[delegate setRefusedRows:[2]];
|
||||
[self clickRow:2];
|
||||
|
||||
[self assert:[] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testDelegateCanReplaceTheProposedSelection
|
||||
{
|
||||
[tableView setDelegate:[[_CPTableInteractionProposalDelegate alloc] init]];
|
||||
[self clickRow:1];
|
||||
|
||||
[self assert:[3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testAClickReportsOneSelectionChange
|
||||
{
|
||||
[self clickRow:2];
|
||||
|
||||
var changes = [delegate calls].filter(function(aCall)
|
||||
{
|
||||
return aCall === @"selectionDidChange";
|
||||
});
|
||||
|
||||
[self assert:1 equals:changes.length];
|
||||
}
|
||||
|
||||
- (void)testClickingTheSelectedRowReportsNoChange
|
||||
{
|
||||
[self clickRow:2];
|
||||
[delegate setCalls:[]];
|
||||
[self clickRow:2];
|
||||
|
||||
[self assertTrue:[delegate calls].indexOf(@"selectionDidChange") === -1];
|
||||
}
|
||||
|
||||
- (void)testSelectAllSelectsEveryRow
|
||||
{
|
||||
[tableView selectAll:nil];
|
||||
|
||||
[self assert:[0, 1, 2, 3, 4] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testDownArrowMovesTheSelection
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self pressKey:CPDownArrowFunctionKey flags:0];
|
||||
|
||||
[self assert:[2] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testUpArrowMovesTheSelection
|
||||
{
|
||||
[self clickRow:2];
|
||||
[self pressKey:CPUpArrowFunctionKey flags:0];
|
||||
|
||||
[self assert:[1] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testArrowStopsAtTheLastRow
|
||||
{
|
||||
[self clickRow:4];
|
||||
[self pressKey:CPDownArrowFunctionKey flags:0];
|
||||
|
||||
[self assert:[4] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testArrowSelectsAnEndRowWhenNothingIsSelected
|
||||
{
|
||||
[self pressKey:CPDownArrowFunctionKey flags:0];
|
||||
[self assert:[0] equals:[self selectedRows]];
|
||||
|
||||
[tableView deselectAll:nil];
|
||||
[self pressKey:CPUpArrowFunctionKey flags:0];
|
||||
[self assert:[4] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testShiftArrowExtendsAndShrinksTheSelection
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self pressKey:CPDownArrowFunctionKey flags:CPShiftKeyMask];
|
||||
|
||||
[self assert:[1, 2] equals:[self selectedRows]];
|
||||
|
||||
[self pressKey:CPUpArrowFunctionKey flags:CPShiftKeyMask];
|
||||
|
||||
[self assert:[1] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testArrowSkipsARefusedRow
|
||||
{
|
||||
[delegate setRefusedRows:[2]];
|
||||
[self clickRow:1];
|
||||
[self pressKey:CPDownArrowFunctionKey flags:0];
|
||||
|
||||
[self assert:[3] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testArrowRespectsARefusedSelectionChange
|
||||
{
|
||||
[self clickRow:1];
|
||||
[delegate setAllowsSelectionChange:NO];
|
||||
[self pressKey:CPDownArrowFunctionKey flags:0];
|
||||
|
||||
[self assert:[1] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
// MARK: Editing
|
||||
|
||||
- (void)testEditingAnUnselectedRowRaises
|
||||
{
|
||||
[self assertThrows:function()
|
||||
{
|
||||
[tableView editColumn:0 row:1 withEvent:nil select:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testEditingReportsTheEditedCell
|
||||
{
|
||||
[self clickRow:1];
|
||||
[tableView editColumn:0 row:1 withEvent:nil select:YES];
|
||||
|
||||
[self assert:0 equals:[tableView editedColumn]];
|
||||
[self assert:1 equals:[tableView editedRow]];
|
||||
}
|
||||
|
||||
- (void)testEditingMakesTheCellTheFirstResponder
|
||||
{
|
||||
[self clickRow:1];
|
||||
[tableView editColumn:0 row:1 withEvent:nil select:YES];
|
||||
|
||||
[self assertTrue:[[theWindow firstResponder] isKindOfClass:[CPTextField class]]];
|
||||
[self assert:@"One" equals:[[theWindow firstResponder] stringValue]];
|
||||
}
|
||||
|
||||
- (void)testEndingAnEditCommitsTheValue
|
||||
{
|
||||
[self clickRow:1];
|
||||
[tableView editColumn:0 row:1 withEvent:nil select:YES];
|
||||
|
||||
var field = [theWindow firstResponder];
|
||||
|
||||
[field setStringValue:@"Edited"];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:field];
|
||||
|
||||
[self assert:1 equals:[dataSource editedValues].length];
|
||||
[self assert:1 equals:[dataSource editedValues][0].row];
|
||||
[self assert:@"Edited" equals:[dataSource editedValues][0].value];
|
||||
[self assert:tableView equals:[theWindow firstResponder]];
|
||||
}
|
||||
|
||||
- (void)testDoubleClickStartsEditing
|
||||
{
|
||||
[self clickRow:1];
|
||||
[self clickPoint:[self pointInRow:1] flags:0 clicks:2];
|
||||
|
||||
[self assert:1 equals:[tableView editedRow]];
|
||||
[self assertTrue:[delegate calls].indexOf(@"shouldEdit") !== -1];
|
||||
}
|
||||
|
||||
- (void)testDelegateCanRefuseAnEdit
|
||||
{
|
||||
[delegate setAllowsEditing:NO];
|
||||
[self clickRow:1];
|
||||
[self clickPoint:[self pointInRow:1] flags:0 clicks:2];
|
||||
|
||||
[self assert:CPNotFound equals:[tableView editedRow]];
|
||||
}
|
||||
|
||||
- (void)testAColumnThatIsNotEditableDoesNotStartAnEdit
|
||||
{
|
||||
[tableColumn setEditable:NO];
|
||||
[self clickRow:1];
|
||||
[self clickPoint:[self pointInRow:1] flags:0 clicks:2];
|
||||
|
||||
[self assert:CPNotFound equals:[tableView editedRow]];
|
||||
}
|
||||
|
||||
// MARK: Starting a drag
|
||||
|
||||
- (void)dragFromRow:(int)aRow toPoint:(CGPoint)aPoint
|
||||
{
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDown atPoint:[self pointInRow:aRow] flags:0 clicks:1]];
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDragged atPoint:aPoint flags:0 clicks:1]];
|
||||
}
|
||||
|
||||
- (void)testADraggableTableSelectsOnMouseUp
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseDown atPoint:[self pointInRow:2] flags:0 clicks:1]];
|
||||
[self assert:[] equals:[self selectedRows]];
|
||||
|
||||
[self sendEvent:[self mouseEvent:CPLeftMouseUp atPoint:[self pointInRow:2] flags:0 clicks:1]];
|
||||
[self assert:[2] equals:[self selectedRows]];
|
||||
}
|
||||
|
||||
- (void)testDraggingARowStartsADrag
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
var start = [self pointInRow:2];
|
||||
|
||||
[self dragFromRow:2 toPoint:CGPointMake(start.x + 20.0, start.y)];
|
||||
|
||||
[self assert:1 equals:[[tableView dragCalls] count]];
|
||||
[self assert:[CPIndexSet indexSetWithIndex:2] equals:[dataSource writtenRows]];
|
||||
[self assert:[InteractionDragType] equals:[[tableView dragCalls][0].pasteboard types]];
|
||||
[self assert:tableView equals:[tableView dragCalls][0].source];
|
||||
}
|
||||
|
||||
- (void)testDraggingASelectedRowDragsTheWholeSelection
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(1, 2)] byExtendingSelection:NO];
|
||||
|
||||
var start = [self pointInRow:2];
|
||||
|
||||
[self dragFromRow:2 toPoint:CGPointMake(start.x + 20.0, start.y)];
|
||||
|
||||
[self assert:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(1, 2)] equals:[dataSource writtenRows]];
|
||||
}
|
||||
|
||||
- (void)testAMoveWithinTheClickSlopDoesNotStartADrag
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
var start = [self pointInRow:2];
|
||||
|
||||
[self dragFromRow:2 toPoint:CGPointMake(start.x + 2.0, start.y)];
|
||||
|
||||
[self assertNull:[tableView dragCalls]];
|
||||
}
|
||||
|
||||
- (void)testDataSourceCanRefuseADrag
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[dataSource setAllowsDrag:NO];
|
||||
|
||||
var start = [self pointInRow:2];
|
||||
|
||||
[self dragFromRow:2 toPoint:CGPointMake(start.x + 20.0, start.y)];
|
||||
|
||||
[self assertNull:[tableView dragCalls]];
|
||||
}
|
||||
|
||||
// MARK: Dropping
|
||||
|
||||
- (void)testAnUnregisteredTypeIsRefused
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[tableView unregisterDraggedTypes];
|
||||
|
||||
[self assert:CPDragOperationNone equals:[tableView draggingEntered:[self draggingInfoAtPoint:[self pointInRow:2]]]];
|
||||
[self assert:0 equals:[dataSource validations].length];
|
||||
}
|
||||
|
||||
- (void)testARegisteredTypeIsValidated
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
[self assert:CPDragOperationMove equals:[tableView draggingEntered:[self draggingInfoAtPoint:[self pointInRow:2]]]];
|
||||
[self assert:1 equals:[dataSource validations].length];
|
||||
}
|
||||
|
||||
- (void)testValidationCanRefuseADrop
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[dataSource setValidation:CPDragOperationNone];
|
||||
|
||||
[self assert:CPDragOperationNone equals:[tableView draggingUpdated:[self draggingInfoAtPoint:[self pointInRow:2]]]];
|
||||
}
|
||||
|
||||
- (void)testTheMiddleOfARowProposesADropOnThatRow
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[tableView draggingUpdated:[self draggingInfoAtPoint:[self pointInRow:2]]];
|
||||
|
||||
[self assert:2 equals:[dataSource validations][0].row];
|
||||
[self assert:CPTableViewDropOn equals:[dataSource validations][0].operation];
|
||||
}
|
||||
|
||||
- (void)testTheTopOfARowProposesADropAboveThatRow
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
var rect = [tableView rectOfRow:2];
|
||||
|
||||
[tableView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(20.0, CGRectGetMinY(rect) + 1.0)]];
|
||||
|
||||
[self assert:2 equals:[dataSource validations][0].row];
|
||||
[self assert:CPTableViewDropAbove equals:[dataSource validations][0].operation];
|
||||
}
|
||||
|
||||
- (void)testTheBottomOfARowProposesADropAboveTheNextRow
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
var rect = [tableView rectOfRow:2];
|
||||
|
||||
[tableView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(20.0, CGRectGetMaxY(rect) - 1.0)]];
|
||||
|
||||
[self assert:3 equals:[dataSource validations][0].row];
|
||||
[self assert:CPTableViewDropAbove equals:[dataSource validations][0].operation];
|
||||
}
|
||||
|
||||
- (void)testBelowTheLastRowProposesADropAtTheEnd
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[tableView draggingUpdated:[self draggingInfoAtPoint:CGPointMake(20.0, 290.0)]];
|
||||
|
||||
[self assert:5 equals:[dataSource validations][0].row];
|
||||
[self assert:CPTableViewDropAbove equals:[dataSource validations][0].operation];
|
||||
}
|
||||
|
||||
- (void)testAcceptingADropReceivesTheProposedRow
|
||||
{
|
||||
[self useDragDataSource];
|
||||
|
||||
var info = [self draggingInfoAtPoint:[self pointInRow:2]];
|
||||
|
||||
[tableView draggingUpdated:info];
|
||||
|
||||
[self assertTrue:[tableView prepareForDragOperation:info]];
|
||||
[self assertTrue:[tableView performDragOperation:info]];
|
||||
[self assert:2 equals:[dataSource acceptedDrops][0].row];
|
||||
[self assert:CPTableViewDropOn equals:[dataSource acceptedDrops][0].operation];
|
||||
}
|
||||
|
||||
- (void)testDataSourceCanRefuseADrop
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[dataSource setAcceptsDrop:NO];
|
||||
|
||||
var info = [self draggingInfoAtPoint:[self pointInRow:2]];
|
||||
|
||||
[tableView draggingUpdated:info];
|
||||
|
||||
[self assertFalse:[tableView performDragOperation:info]];
|
||||
}
|
||||
|
||||
- (void)testDataSourceCanRetargetADrop
|
||||
{
|
||||
[self useDragDataSource];
|
||||
[dataSource setRetargetRow:4];
|
||||
[dataSource setRetargetOperation:CPTableViewDropAbove];
|
||||
|
||||
var info = [self draggingInfoAtPoint:[self pointInRow:2]];
|
||||
|
||||
[tableView draggingUpdated:info];
|
||||
[tableView performDragOperation:info];
|
||||
|
||||
[self assert:4 equals:[dataSource acceptedDrops][0].row];
|
||||
[self assert:CPTableViewDropAbove equals:[dataSource acceptedDrops][0].operation];
|
||||
}
|
||||
|
||||
- (void)testPreparingADropNeedsValidation
|
||||
{
|
||||
var info = [self draggingInfoAtPoint:[self pointInRow:2]];
|
||||
|
||||
[self assertFalse:[tableView prepareForDragOperation:info]];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user