mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
796 lines
30 KiB
Plaintext
796 lines
30 KiB
Plaintext
@import <AppKit/AppKit.j>
|
|
|
|
@import "CPNotificationCenterHelper.j"
|
|
|
|
@implementation CPTableViewTest : OJTestCase
|
|
{
|
|
CPWindow theWindow;
|
|
CPTableView tableView;
|
|
CPTableColumn tableColumn;
|
|
|
|
BOOL doubleActionReceived;
|
|
int selectionIsChangingNotificationsReceived;
|
|
int selectionDidChangeNotificationsReceived;
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
// This will init the global var CPApp which are used internally in the AppKit
|
|
[[CPApplication alloc] init];
|
|
|
|
// setup a reasonable table
|
|
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0) styleMask:CPWindowNotSizable];
|
|
|
|
tableView = [[FirstResponderConfigurableTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
|
tableColumn = [[CPTableColumn alloc] initWithIdentifier:@"Foo"];
|
|
tableView.acceptsFirstResponder = YES;
|
|
[tableView addTableColumn:tableColumn];
|
|
|
|
[[theWindow contentView] addSubview:tableView];
|
|
}
|
|
|
|
- (void)testDoubleAction
|
|
{
|
|
doubleActionReceived = NO;
|
|
|
|
[tableView setTarget:self];
|
|
[tableView setDoubleAction:@selector(doubleAction:)];
|
|
|
|
// CPEvent with 2 clickCount
|
|
var dblClkDown = [CPEvent mouseEventWithType:CPLeftMouseDown location:CGPointMake(50, 50) modifierFlags:0
|
|
timestamp:0 windowNumber:[theWindow windowNumber] context:nil eventNumber:0 clickCount:2 pressure:0],
|
|
dblClkUp = [CPEvent mouseEventWithType:CPLeftMouseUp location:CGPointMake(50, 50) modifierFlags:0
|
|
timestamp:0 windowNumber:[theWindow windowNumber] context:nil eventNumber:0 clickCount:2 pressure:0];
|
|
|
|
[[CPApplication sharedApplication] sendEvent:dblClkDown];
|
|
[tableView trackMouse:dblClkDown];
|
|
|
|
[[CPApplication sharedApplication] sendEvent:dblClkUp];
|
|
[tableView trackMouse:dblClkUp];
|
|
|
|
[self assertTrue:doubleActionReceived];
|
|
|
|
// The event should also work even if the table is not the first responder.
|
|
tableView.acceptsFirstResponder = NO;
|
|
[theWindow makeFirstResponder:nil];
|
|
|
|
doubleActionReceived = NO;
|
|
|
|
[[CPApplication sharedApplication] sendEvent:dblClkDown];
|
|
[tableView trackMouse:dblClkDown];
|
|
|
|
[[CPApplication sharedApplication] sendEvent:dblClkUp];
|
|
[tableView trackMouse:dblClkUp];
|
|
|
|
[self assertTrue:doubleActionReceived];
|
|
}
|
|
|
|
- (void)doubleAction:(id)sender
|
|
{
|
|
doubleActionReceived = YES;
|
|
}
|
|
|
|
/*!
|
|
Test that proper notifications are sent - or not sent - during the course of a table data
|
|
change which might affect the selection.
|
|
*/
|
|
- (void)testNumberOfRowsChangedSelectionNotification
|
|
{
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[dataSource setTableEntries:["A", "B", "C"]];
|
|
[tableView setDataSource:dataSource];
|
|
|
|
|
|
selectionIsChangingNotificationsReceived = 0;
|
|
selectionDidChangeNotificationsReceived = 0;
|
|
[[CPNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(selectionIsChanging:)
|
|
name:CPTableViewSelectionIsChangingNotification
|
|
object:tableView];
|
|
|
|
[[CPNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(selectionDidChange:)
|
|
name:CPTableViewSelectionDidChangeNotification
|
|
object:tableView];
|
|
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:2] byExtendingSelection:NO];
|
|
[self assert:0 equals:selectionIsChangingNotificationsReceived message:"no isChanging notifications when programmatically selecting rows"];
|
|
[self assert:1 equals:selectionDidChangeNotificationsReceived message:"didChange notifications when selecting rows"];
|
|
|
|
selectionIsChangingNotificationsReceived = 0;
|
|
selectionDidChangeNotificationsReceived = 0;
|
|
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
|
|
[self assert:selectionDidChangeNotificationsReceived equals:1 message:"CPTableViewSelectionDidChangeNotification expected when selecting rows"];
|
|
[[dataSource tableEntries] removeObjectAtIndex:1];
|
|
[tableView reloadData];
|
|
|
|
[self assert:selectionDidChangeNotificationsReceived equals:1 message:"no CPTableViewSelectionDidChangeNotification expected when removing a row which does not change the selection"];
|
|
|
|
// Reset everything.
|
|
[dataSource setTableEntries:["A", "B", "C"]];
|
|
[tableView reloadData];
|
|
[tableView deselectAll];
|
|
selectionIsChangingNotificationsReceived = 0;
|
|
selectionDidChangeNotificationsReceived = 0;
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:2] byExtendingSelection:NO];
|
|
[self assert:2 equals:selectionDidChangeNotificationsReceived message:"notification for select row 0, 2"];
|
|
|
|
selectionIsChangingNotificationsReceived = 0;
|
|
selectionDidChangeNotificationsReceived = 0;
|
|
[tableView deselectAll];
|
|
[self assert:1 equals:selectionDidChangeNotificationsReceived message:"notification for deselect all"];
|
|
|
|
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:2] byExtendingSelection:NO];
|
|
selectionIsChangingNotificationsReceived = 0;
|
|
selectionDidChangeNotificationsReceived = 0;
|
|
// If we remove the last row, the selection should change and we should be notified.
|
|
[[dataSource tableEntries] removeObjectAtIndex:2];
|
|
[tableView reloadData];
|
|
|
|
[self assert:0 equals:selectionIsChangingNotificationsReceived message:"no isChanging notifications when selected rows disappear"];
|
|
[self assert:1 equals:selectionDidChangeNotificationsReceived message:"didChange notifications when selected rows disappear"];
|
|
}
|
|
|
|
- (void)selectionIsChanging:(CPNotification)aNotification
|
|
{
|
|
selectionIsChangingNotificationsReceived++;
|
|
}
|
|
|
|
- (void)selectionDidChange:(CPNotification)aNotification
|
|
{
|
|
selectionDidChangeNotificationsReceived++;
|
|
}
|
|
|
|
/*!
|
|
Test inline table editing.
|
|
*/
|
|
- (void)testEditCell
|
|
{
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[dataSource setTableEntries:["A", "B", "C"]];
|
|
[tableView setDataSource:dataSource];
|
|
[tableView setDelegate:[EditableTableDelegate new]];
|
|
|
|
[theWindow makeFirstResponder:tableView];
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
|
|
[tableView editColumn:0 row:1 withEvent:nil select:YES];
|
|
|
|
// Now some text field should be the first responder.
|
|
var fieldEditor = [theWindow firstResponder];
|
|
[self assert:CPTextField equals:[fieldEditor class] message:"table cell editor should be a text field"];
|
|
|
|
[fieldEditor setStringValue:"edited text"];
|
|
[theWindow makeFirstResponder:tableView];
|
|
|
|
[self assert:"edited text" equals:[dataSource tableEntries][1] message:"table cell edit should propagate to model"]
|
|
|
|
// The first responder status should revert to the table view so that, for example, the user may continue
|
|
// keyboard navigation to edit the next row.
|
|
[self assert:[theWindow firstResponder] equals:tableView message:"table view should be first responder after cell edit"];
|
|
}
|
|
|
|
/*!
|
|
Verify that all data appears as expected, using the right data views etc. This is a bit of a kitchen
|
|
sink test, verifying multiple behaviours of the table view.
|
|
*/
|
|
- (void)testLayout
|
|
{
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)],
|
|
tableColumn1 = [[CPTableColumn alloc] initWithIdentifier:@"Bar"];
|
|
|
|
[tableView addTableColumn:tableColumn1];
|
|
[scrollView setDocumentView:tableView];
|
|
|
|
[tableColumn setWidth:50.0];
|
|
[tableColumn1 setWidth:50.0];
|
|
|
|
var arrayController = [CPArrayController new],
|
|
contentArray = [];
|
|
|
|
for (var row = 0; row < 50; row++)
|
|
{
|
|
var column = row * 2;
|
|
contentArray.push([CPDictionary dictionaryWithObjects:["R" + row + "C0", "R" + row + "C1"] forKeys:["c1", "c2"]]);
|
|
}
|
|
[arrayController setContent:contentArray];
|
|
|
|
[tableColumn bind:CPValueBinding toObject:arrayController withKeyPath:@"arrangedObjects.c1" options:nil];
|
|
[tableColumn1 bind:CPValueBinding toObject:arrayController withKeyPath:@"arrangedObjects.c2" options:nil];
|
|
|
|
[tableColumn setDataView:[CustomTextView0 new]];
|
|
[tableColumn1 setDataView:[CustomTextView1 new]];
|
|
|
|
|
|
[tableView reloadData];
|
|
// Process all events immediately to make sure table data views are reloaded.
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
[self assert:50 equals:[tableView numberOfRows] message:"tableView numberOfRows should reflect content array length"];
|
|
|
|
function AssertCorrectCellsVisible(startingRow)
|
|
{
|
|
var allViews = getAllViews(tableView);
|
|
|
|
// Sort views from left to right, top to bottom.
|
|
[allViews sortUsingFunction:keyViewComparator context:nil];
|
|
|
|
// We only care about the placement of data views - what else the table puts in there is up to it.
|
|
[allViews filterUsingPredicate:[CPPredicate predicateWithFormat:@"self.class.description beginswith %@", "CustomTextView"]];
|
|
|
|
for (var i = 0; i < [allViews count]; i++)
|
|
{
|
|
var view = allViews[i],
|
|
column = i % 2,
|
|
row = startingRow + FLOOR(i / 2); // Two columns per row.
|
|
//CPLog.error([view stringValue] + " frame: " + CPStringFromRect([tableView convertRect:[view frame] toView:nil]));
|
|
[self assert:("R" + row + "C" + column) equals:[view stringValue] message:"(" + row + ", " + column + ") string value"];
|
|
[self assert:"CustomTextView" + column equals:[[view class] description] message:"(" + row + ", " + column + ") data view"];
|
|
}
|
|
|
|
return allViews;
|
|
}
|
|
|
|
var allViews = AssertCorrectCellsVisible(0),
|
|
visibleHeight = [tableView visibleRect].size.height,
|
|
fullRowHeight = [tableView rowHeight] + [tableView intercellSpacing].height,
|
|
visibleRows = CEIL(visibleHeight / fullRowHeight);
|
|
|
|
[self assert:2 * visibleRows equals:[allViews count] message:"only as many data views as necessary should be present"];
|
|
|
|
// Now if we scroll down, new views should come in and others should go out.
|
|
var rowTwentyFiveAndAHalfY = FLOOR(25.5 * fullRowHeight);
|
|
[tableView scrollPoint:CGPointMake(0, rowTwentyFiveAndAHalfY)];
|
|
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
AssertCorrectCellsVisible(25);
|
|
[self assert:2 * visibleRows equals:[allViews count] message:"only as many data views as necessary should be present (2)"];
|
|
}
|
|
|
|
- (void)testContentBinding
|
|
{
|
|
var contentBindingTable = [[CPTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)],
|
|
delegate = [ContentBindingTableDelegate new];
|
|
|
|
tableColumn = [[CPTableColumn alloc] initWithIdentifier:@"A"];
|
|
|
|
[contentBindingTable addTableColumn:tableColumn];
|
|
[delegate setTester:self];
|
|
[contentBindingTable setDelegate:delegate];
|
|
|
|
[delegate setTableEntries:[[@"A1", @"B1"], [@"A2", @"B2"], [@"A3", @"B3"]]];
|
|
[contentBindingTable bind:@"content" toObject:delegate withKeyPath:@"tableEntries" options:nil];
|
|
|
|
// The following should also work:
|
|
//var ac = [[CPArrayController alloc] initWithContent:[delegate tableEntries]];
|
|
//[contentBindingTable bind:@"content" toObject:ac withKeyPath:@"arrangedObjects" options:nil];
|
|
|
|
[[theWindow contentView] addSubview:contentBindingTable];
|
|
[theWindow makeFirstResponder:contentBindingTable];
|
|
|
|
// Set the model again with different values
|
|
[delegate setTableEntries:[[@"C1", @"D1"], [@"C2", @"D2"], [@"C3", @"D3"]]];
|
|
[contentBindingTable reloadData];
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
}
|
|
|
|
- (void)testColumnValueBinding
|
|
{
|
|
var table = [[CPTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)],
|
|
container = [TestDataSource new];
|
|
|
|
var tc = [[CPTableColumn alloc] initWithIdentifier:@"A"];
|
|
[table addTableColumn:tc];
|
|
|
|
[container setTableEntries:@[@{@"name":@"B1"}, @{@"name":@"B2"}, @{@"name":@"B3"}]];
|
|
|
|
var ac = [[CPArrayController alloc] init];
|
|
[ac bind:@"contentArray" toObject:container withKeyPath:@"tableEntries" options:nil];
|
|
[tc bind:@"value" toObject:ac withKeyPath:@"arrangedObjects.name" options:nil];
|
|
|
|
[[theWindow contentView] addSubview:table];
|
|
[theWindow makeFirstResponder:table];
|
|
|
|
// Should remove all table rows.
|
|
[self assertNoThrow:function()
|
|
{
|
|
[container setTableEntries:@[]];
|
|
}];
|
|
|
|
[container setTableEntries:@[@{@"name":@"B1"}, @{@"name":@"B2"}, @{@"name":@"B3"}]];
|
|
|
|
[self assertNoThrow:function()
|
|
{
|
|
[container setTableEntries:nil];
|
|
}];
|
|
|
|
[container setTableEntries:@[@{@"name":@"B1"}, @{@"name":@"B2"}, @{@"name":@"B3"}]];
|
|
|
|
// Change a value in row 0. The number of rows stays the same.
|
|
[container setTableEntries:@[@{@"name":@"B4"}, @{@"name":@"B2"}, @{@"name":@"B3"}]];
|
|
|
|
var enumerateViewsInRowsCall = 0;
|
|
|
|
// Checks that the displayed data matches the model data.
|
|
[table enumerateAvailableViewsUsingBlock:function(dataView, aRow, aColumn, stop)
|
|
{
|
|
enumerateViewsInRowsCall++;
|
|
var data_value = [[[container tableEntries] objectAtIndex:aRow] objectForKey:@"name"];
|
|
[self assert:[dataView objectValue] equals:data_value];
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:3];
|
|
|
|
// We could also check that the dataviews in rows 1 and 2 are identical to the ones before mutation.
|
|
}
|
|
|
|
- (void)testInitiallyHiddenColumns
|
|
{
|
|
var table = [[CPTableView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)],
|
|
tableColumn1 = [[CPTableColumn alloc] initWithIdentifier:@"A"],
|
|
tableColumn2 = [[CPTableColumn alloc] initWithIdentifier:@"B"],
|
|
delegate = [ContentBindingTableDelegate new];
|
|
|
|
[delegate setTester:self];
|
|
[table setDelegate:delegate];
|
|
|
|
[delegate setTableEntries:[[@"A1", @"B1"], [@"A2", @"B2"], [@"A3", @"B3"]]];
|
|
[table bind:@"content" toObject:delegate withKeyPath:@"tableEntries" options:nil];
|
|
|
|
[[theWindow contentView] addSubview:table];
|
|
|
|
[tableColumn1 setHidden:YES];
|
|
|
|
[tableColumn1 setWidth:50.0];
|
|
[tableColumn2 setWidth:100.0];
|
|
|
|
[table addTableColumn:tableColumn1];
|
|
[table addTableColumn:tableColumn2];
|
|
|
|
[table reloadData];
|
|
|
|
[self assertTrue:[table bounds].size.width > 100 && [table bounds].size.width < 200];
|
|
|
|
[tableColumn1 setHidden:NO];
|
|
[tableColumn1 setWidth:100.0];
|
|
|
|
[self assertTrue:[table bounds].size.width >= 200];
|
|
}
|
|
|
|
// Test internal method - (void)getColumn:(Function)columnRef row:(Function)rowRef forView:(CPView)aView
|
|
- (void)testGetColumnAndRow
|
|
{
|
|
var table = [[CPTableView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)],
|
|
tableColumn1 = [[CPTableColumn alloc] initWithIdentifier:@"A"],
|
|
tableColumn2 = [[CPTableColumn alloc] initWithIdentifier:@"B"],
|
|
delegate = [ContentBindingTableDelegate new];
|
|
|
|
[delegate setTester:self];
|
|
[table setDelegate:delegate];
|
|
|
|
[delegate setTableEntries:[[@"A1", @"B1"], [@"A2", @"B2"], [@"A3", @"B3"]]];
|
|
[table bind:@"content" toObject:delegate withKeyPath:@"tableEntries" options:nil];
|
|
|
|
[[theWindow contentView] addSubview:table];
|
|
|
|
[tableColumn1 setWidth:50.0];
|
|
[tableColumn2 setWidth:100.0];
|
|
|
|
[table addTableColumn:tableColumn1];
|
|
[table addTableColumn:tableColumn2];
|
|
|
|
var table2 = [[CPTableView alloc] initWithFrame:CGRectMake(400, 0, 400, 400)],
|
|
table2Column1 = [[CPTableColumn alloc] initWithIdentifier:@"A"],
|
|
table2Column2 = [[CPTableColumn alloc] initWithIdentifier:@"B"],
|
|
delegate = [ContentBindingTableDelegate new];
|
|
|
|
[delegate setTester:self];
|
|
[table2 setDelegate:delegate];
|
|
|
|
[delegate setTableEntries:[[@"A1", @"B1"], [@"A2", @"B2"], [@"A3", @"B3"]]];
|
|
[table2 bind:@"content" toObject:delegate withKeyPath:@"tableEntries" options:nil];
|
|
|
|
[[theWindow contentView] addSubview:table2];
|
|
|
|
[table2Column1 setWidth:50.0];
|
|
[table2Column2 setWidth:100.0];
|
|
|
|
[table2 addTableColumn:tableColumn1];
|
|
[table2 addTableColumn:tableColumn2];
|
|
|
|
var row,
|
|
column;
|
|
|
|
// get row and column for a nil view
|
|
[table getColumn:@ref(column) row:@ref(row) forView:nil];
|
|
|
|
[self assert:CPNotFound equals:column];
|
|
[self assert:CPNotFound equals:row];
|
|
|
|
// get row and column for a random view
|
|
var v = [[CPView alloc] initWithFrame:CGRectMake(0,0,100,100)];
|
|
|
|
[table getColumn:@ref(column) row:@ref(row) forView:v];
|
|
|
|
[self assert:CPNotFound equals:column];
|
|
[self assert:CPNotFound equals:row];
|
|
|
|
// get row and column for the table view
|
|
[table getColumn:@ref(column) row:@ref(row) forView:table];
|
|
|
|
[self assert:CPNotFound equals:column];
|
|
[self assert:CPNotFound equals:row];
|
|
|
|
// get row and column for a view outside a table column
|
|
[table getColumn:@ref(column) row:@ref(row) forView:[theWindow contentView]];
|
|
|
|
[self assert:CPNotFound equals:column];
|
|
[self assert:CPNotFound equals:row];
|
|
|
|
var enumerateViewsInRowsCall = 0;
|
|
|
|
// Enumerate views inside the table view and check that rows and columns are correct
|
|
[table enumerateAvailableViewsUsingBlock:function(dataView, aRow, aColumn, stop)
|
|
{
|
|
enumerateViewsInRowsCall++;
|
|
|
|
var getRow,
|
|
getColumn;
|
|
|
|
[table getColumn:@ref(getColumn) row:@ref(getRow) forView:dataView];
|
|
|
|
[self assert:aColumn equals:getColumn];
|
|
[self assert:aRow equals:getRow];
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:6];
|
|
|
|
enumerateViewsInRowsCall = 0;
|
|
|
|
// Enumerate views inside a different table view and check that rows and columns are not found
|
|
[table2 enumerateAvailableViewsUsingBlock:function(dataView, aRow, aColumn, stop)
|
|
{
|
|
enumerateViewsInRowsCall++;
|
|
|
|
var getRow,
|
|
getColumn;
|
|
|
|
[table getColumn:@ref(getColumn) row:@ref(getRow) forView:dataView];
|
|
|
|
[self assert:CPNotFound equals:getRow];
|
|
[self assert:CPNotFound equals:getColumn];
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:6];
|
|
}
|
|
|
|
-(void)testNotificationsRegistered
|
|
{
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:[@"_CPWindowDidChangeFirstResponderNotification"] message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
[tableView removeFromSuperview];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:[] message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
[[theWindow contentView] addSubview:tableView];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:[@"_CPWindowDidChangeFirstResponderNotification"] message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
[[theWindow contentView] addSubview:tableView];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:[@"_CPWindowDidChangeFirstResponderNotification"] message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)],
|
|
expectedNotifications = [@"_CPWindowDidChangeFirstResponderNotification", @"CPViewFrameDidChangeNotification", @"CPViewBoundsDidChangeNotification"].sort();
|
|
|
|
[scrollView setDocumentView:tableView];
|
|
|
|
[[theWindow contentView] addSubview:scrollView];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:expectedNotifications message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
[[theWindow contentView] addSubview:scrollView];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:expectedNotifications message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
|
|
[scrollView removeFromSuperview];
|
|
[self assert:[CPNotificationCenterHelper registeredNotificationsForObserver:tableView] equals:[] message:@"Notications registered for the tableView in the notification center are wrong"];
|
|
}
|
|
|
|
- (void)testTableDataViewState
|
|
{
|
|
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(110, 0, 100, 32)];
|
|
[textField setEditable:YES];
|
|
[[theWindow contentView] addSubview:textField];
|
|
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[dataSource setTableEntries:["A", "B", "C"]];
|
|
[tableView setDataSource:dataSource];
|
|
[tableView setDelegate:[EditableTableDelegate new]];
|
|
|
|
[theWindow makeFirstResponder:tableView];
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
|
|
|
|
var enumerateViewsInRowsCall = 0;
|
|
|
|
[tableView enumerateAvailableViewsUsingBlock:function(dataView, row, column, stop)
|
|
{
|
|
if (row == 0)
|
|
{
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be enabled"];
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
|
|
}
|
|
|
|
if (row == 1)
|
|
{
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
|
|
[self assertFalse:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be disabled"];
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
|
|
}
|
|
|
|
enumerateViewsInRowsCall++;
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:3];
|
|
|
|
[tableView selectRowIndexes:[CPIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
|
|
|
|
enumerateViewsInRowsCall = 0;
|
|
|
|
[tableView enumerateAvailableViewsUsingBlock:function(dataView, row, column, stop)
|
|
{
|
|
enumerateViewsInRowsCall++;
|
|
|
|
if (row == 0)
|
|
{
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
|
|
[self assertFalse:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be disabled"];
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be enabled"];
|
|
}
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:3];
|
|
|
|
[theWindow makeFirstResponder:textField];
|
|
|
|
enumerateViewsInRowsCall = 0;
|
|
|
|
[tableView enumerateAvailableViewsUsingBlock:function(dataView, row, column, stop)
|
|
{
|
|
enumerateViewsInRowsCall++;
|
|
|
|
if (row == 0)
|
|
{
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
|
|
[self assertFalse:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be disabled"];
|
|
[self assertFalse:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be disabled"];
|
|
}
|
|
|
|
if (row == 1)
|
|
{
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateTableDataView] message:"CPThemeStateTableDataView should be enabled"];
|
|
[self assertTrue:[dataView hasThemeState:CPThemeStateSelectedDataView] message:"CPThemeStateSelectedDataView should be enabled"];
|
|
[self assertFalse:[dataView hasThemeState:CPThemeStateFirstResponder] message:"CPThemeStateFirstResponder should be disabled"];
|
|
}
|
|
}];
|
|
|
|
[self assert:enumerateViewsInRowsCall equals:3];
|
|
}
|
|
|
|
- (void)testMethodViewAtColumnWithMakeIfNecessarySetToNo
|
|
{
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)];
|
|
[scrollView setDocumentView:tableView];
|
|
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[tableView setDelegate:[CustomeSizeTableDelegate new]];
|
|
[dataSource setTableEntries:["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]];
|
|
[tableView setDataSource:dataSource];
|
|
|
|
var view = [tableView viewAtColumn:0 row:0 makeIfNecessary:NO];
|
|
[self assert:view equals:nil message:@"View should be equal to nil"];
|
|
|
|
// Process all events immediately to make sure table data views are reloaded.
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
var view = [tableView viewAtColumn:0 row:0 makeIfNecessary:NO];
|
|
[self assert:[view objectValue] equals:@"A" message:@"View should be equal to A"];
|
|
[self assert:[view superview] equals:tableView message:@"Superview of view should be the tableview"];
|
|
|
|
view = [tableView viewAtColumn:0 row:25 makeIfNecessary:NO];
|
|
[self assert:view equals:nil message:@"View should be equal to nil"];
|
|
}
|
|
|
|
- (void)testMethodViewAtColumnWithMakeIfNecessarySetToYes
|
|
{
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)];
|
|
[scrollView setDocumentView:tableView];
|
|
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[tableView setDelegate:[CustomeSizeTableDelegate new]];
|
|
[dataSource setTableEntries:["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]];
|
|
[tableView setDataSource:dataSource];
|
|
|
|
var view = [tableView viewAtColumn:0 row:0 makeIfNecessary:YES];
|
|
[self assert:[view objectValue] equals:@"A" message:@"View should be equal to A"];
|
|
[self assert:[view superview] equals:tableView message:@"Superview of view should be the tableview"];
|
|
|
|
view = [tableView viewAtColumn:0 row:25 makeIfNecessary:YES];
|
|
[self assert:[view objectValue] equals:@"Z" message:@"View should be equal to Z"];
|
|
[self assert:[view superview] equals:tableView message:@"Superview of view should be the tableview"];
|
|
}
|
|
|
|
- (void)testMethodViewAtColumnException
|
|
{
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)];
|
|
[scrollView setDocumentView:tableView];
|
|
|
|
var dataSource = [TestDataSource new];
|
|
|
|
[tableView setDelegate:[CustomeSizeTableDelegate new]];
|
|
[dataSource setTableEntries:["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]];
|
|
[tableView setDataSource:dataSource];
|
|
|
|
var expectedMessage = @"Row 26 out of row range [0-25] for rowViewAtRow:createIfNeeded:",
|
|
exceptionMessage = @"";
|
|
|
|
try
|
|
{
|
|
[tableView viewAtColumn:0 row:26 makeIfNecessary:YES];
|
|
}
|
|
catch (e)
|
|
{
|
|
exceptionMessage = e.message;
|
|
}
|
|
|
|
[self assert:expectedMessage equals:exceptionMessage];
|
|
|
|
|
|
expectedMessage = @"Column 2 out of row range [0-0] for rowViewAtRow:createIfNeeded:";
|
|
|
|
try
|
|
{
|
|
[tableView viewAtColumn:2 row:2 makeIfNecessary:YES];
|
|
}
|
|
catch (e)
|
|
{
|
|
exceptionMessage = e.message;
|
|
}
|
|
|
|
[self assert:expectedMessage equals:exceptionMessage];
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation FirstResponderConfigurableTableView : CPTableView
|
|
{
|
|
BOOL acceptsFirstResponder;
|
|
}
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return acceptsFirstResponder;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation TestDataSource : CPObject <CPTableViewDataSource>
|
|
{
|
|
CPArray tableEntries @accessors;
|
|
}
|
|
|
|
- (int)numberOfRowsInTableView:(CPTableView)aTableView
|
|
{
|
|
return [tableEntries count];
|
|
}
|
|
|
|
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
|
|
{
|
|
return tableEntries[aRow];
|
|
}
|
|
|
|
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
|
|
{
|
|
tableEntries[aRow] = anObject;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CustomeSizeTableDelegate : CPObject
|
|
{
|
|
|
|
}
|
|
|
|
- (float)tableView:(CPTableView)aTableView heightOfRow:(CPInteger)aRowIndex
|
|
{
|
|
return 200;
|
|
}
|
|
|
|
- (CPView)tableView:(CPTableView)aTableView viewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
|
|
{
|
|
return [[CPTextField alloc] initWithFrame:CGRectMake(0,0, 100, 100)];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation EditableTableDelegate : CPObject
|
|
{
|
|
}
|
|
|
|
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)anRow
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ContentBindingTableDelegate : CPObject
|
|
{
|
|
// For the purpose of this test, the delegate contains the model (!).
|
|
CPArray tableEntries @accessors;
|
|
CPTableViewTest tester @accessors;
|
|
}
|
|
|
|
- (void)tableView:(CPTableView)aTableView willDisplayView:(CPView)aView forTableColumn:(CPTableColumn)tableColumn row:(CPInteger)row
|
|
{
|
|
// Make sure each view contains the full row in its objectValue
|
|
[tester assert:tableEntries[row] equals:[aView objectValue]];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CustomTextView0 : CPTextField
|
|
@end
|
|
|
|
@implementation CustomTextView1 : CPTextField
|
|
@end
|
|
|
|
var getAllViews = function(aView)
|
|
{
|
|
var views = [aView],
|
|
subviews = [aView subviews];
|
|
|
|
for (var i = 0, count = [subviews count]; i < count; i++)
|
|
views = views.concat(getAllViews(subviews[i]));
|
|
|
|
return views;
|
|
};
|
|
|
|
var keyViewComparator = function(lhs, rhs, context)
|
|
{
|
|
var lhsBounds = [lhs convertRect:[lhs bounds] toView:nil],
|
|
rhsBounds = [rhs convertRect:[rhs bounds] toView:nil],
|
|
lhsY = CGRectGetMinY(lhsBounds),
|
|
rhsY = CGRectGetMinY(rhsBounds),
|
|
lhsX = CGRectGetMinX(lhsBounds),
|
|
rhsX = CGRectGetMinX(rhsBounds),
|
|
intersectsVertically = MIN(CGRectGetMaxY(lhsBounds), CGRectGetMaxY(rhsBounds)) - MAX(lhsY, rhsY);
|
|
|
|
// If two views are "on the same line" (intersect vertically), then rely on the x comparison.
|
|
if (intersectsVertically > 0)
|
|
{
|
|
if (lhsX < rhsX)
|
|
return CPOrderedAscending;
|
|
|
|
if (lhsX === rhsX)
|
|
return CPOrderedSame;
|
|
|
|
return CPOrderedDescending;
|
|
}
|
|
|
|
if (lhsY < rhsY)
|
|
return CPOrderedAscending;
|
|
|
|
if (lhsY === rhsY)
|
|
return CPOrderedSame;
|
|
|
|
return CPOrderedDescending;
|
|
};
|