From 98c01b88e29e0dbae1297cc4aef3c0ea2014e053 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 7 Mar 2026 20:00:09 +0100 Subject: [PATCH] fixed various binding issues --- AppKit/CPTreeController.j | 377 +++++++++++----------------- Tests/AppKit/CPTreeControllerTest.j | 159 ++++++------ 2 files changed, 224 insertions(+), 312 deletions(-) diff --git a/AppKit/CPTreeController.j b/AppKit/CPTreeController.j index 3274d2a08..aefaa0660 100644 --- a/AppKit/CPTreeController.j +++ b/AppKit/CPTreeController.j @@ -25,12 +25,6 @@ @import "CPKeyValueBinding.j" @import "CPTreeNode.j" -/*! - @class CPTreeController - - CPTreeController is a bindings-compatible class that manages a tree of objects. - It provides selection and sort management for hierarchical data. - */ @implementation CPTreeController : CPObjectController { BOOL _avoidsEmptySelection; @@ -43,10 +37,9 @@ CPString _leafKeyPath; CPArray _sortDescriptors; - id _arrangedObjects; // The proxy root node containing the tree - - CPArray _selectionIndexPaths; // Array of CPIndexPath objects + id _arrangedObjects; + CPArray _selectionIndexPaths; BOOL _disableSetContent; } @@ -91,7 +84,7 @@ + (CPSet)keyPathsForValuesAffectingCanInsert { - return[CPSet setWithObjects:@"selectionIndexPaths"]; + return [CPSet setWithObjects:@"selectionIndexPaths"]; } + (CPSet)keyPathsForValuesAffectingCanInsertChild @@ -101,20 +94,16 @@ - (id)init { - self = [super init]; - - if (self) + if (self = [super init]) { _preservesSelection = YES; _selectsInsertedObjects = YES; _avoidsEmptySelection = YES; _alwaysUsesMultipleValuesMarker = NO; - _childrenKeyPath = @"children"; [self _init]; } - return self; } @@ -126,57 +115,22 @@ } - (void)prepareContent -{ - [self _setContentArray:[[self newObject]]]; +{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]]; } -// --- Properties --- +- (BOOL)preservesSelection { return _preservesSelection; } +- (void)setPreservesSelection:(BOOL)value { _preservesSelection = value; } -- (BOOL)preservesSelection -{ - return _preservesSelection; -} +- (BOOL)selectsInsertedObjects { return _selectsInsertedObjects; } +- (void)setSelectsInsertedObjects:(BOOL)value { _selectsInsertedObjects = value; } -- (void)setPreservesSelection:(BOOL)value -{ - _preservesSelection = value; -} +- (BOOL)avoidsEmptySelection { return _avoidsEmptySelection; } +- (void)setAvoidsEmptySelection:(BOOL)value { _avoidsEmptySelection = value; } -- (BOOL)selectsInsertedObjects -{ - return _selectsInsertedObjects; -} - -- (void)setSelectsInsertedObjects:(BOOL)value -{ - _selectsInsertedObjects = value; -} - -- (BOOL)avoidsEmptySelection -{ - return _avoidsEmptySelection; -} - -- (void)setAvoidsEmptySelection:(BOOL)value -{ - _avoidsEmptySelection = value; -} - -- (BOOL)alwaysUsesMultipleValuesMarker -{ - return _alwaysUsesMultipleValuesMarker; -} - -- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag -{ - _alwaysUsesMultipleValuesMarker = aFlag; -} - -- (CPArray)sortDescriptors -{ - return _sortDescriptors; -} +- (BOOL)alwaysUsesMultipleValuesMarker { return _alwaysUsesMultipleValuesMarker; } +- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag { _alwaysUsesMultipleValuesMarker = aFlag; } +- (CPArray)sortDescriptors { return _sortDescriptors; } - (void)setSortDescriptors:(CPArray)value { if (_sortDescriptors === value) @@ -186,71 +140,31 @@ [self _rearrangeObjects]; } -// --- Key Paths --- - -- (CPString)childrenKeyPath -{ - return _childrenKeyPath; -} - +- (CPString)childrenKeyPath { return _childrenKeyPath; } - (void)setChildrenKeyPath:(CPString)aKeyPath { - if (_childrenKeyPath === aKeyPath) - return; - - _childrenKeyPath = aKeyPath; - [self rearrangeObjects]; + if (_childrenKeyPath === aKeyPath) return; + _childrenKeyPath = aKeyPath;[self rearrangeObjects]; } -- (CPString)countKeyPath -{ - return _countKeyPath; -} +- (CPString)countKeyPath { return _countKeyPath; } +- (void)setCountKeyPath:(CPString)aKeyPath { _countKeyPath = aKeyPath; } -- (void)setCountKeyPath:(CPString)aKeyPath -{ - _countKeyPath = aKeyPath; -} +- (CPString)leafKeyPath { return _leafKeyPath; } +- (void)setLeafKeyPath:(CPString)aKeyPath { _leafKeyPath = aKeyPath; } -- (CPString)leafKeyPath -{ - return _leafKeyPath; -} - -- (void)setLeafKeyPath:(CPString)aKeyPath -{ - _leafKeyPath = aKeyPath; -} - -// --- Node Key Path Overrides --- - -- (CPString)childrenKeyPathForNode:(CPTreeNode)node -{ - return [self childrenKeyPath]; -} - -- (CPString)countKeyPathForNode:(CPTreeNode)node -{ - return [self countKeyPath]; -} - -- (CPString)leafKeyPathForNode:(CPTreeNode)node -{ - return [self leafKeyPath]; -} - -// --- Content and Arranged Objects --- +- (CPString)childrenKeyPathForNode:(CPTreeNode)node { return [self childrenKeyPath]; } +- (CPString)countKeyPathForNode:(CPTreeNode)node { return [self countKeyPath]; } +- (CPString)leafKeyPathForNode:(CPTreeNode)node { return [self leafKeyPath]; } - (void)setContent:(id)value { - if (_disableSetContent) - return; - - if (value == nil) - value = []; + if (_disableSetContent) return; + if (!value) + value = [CPArray array]; if (![value isKindOfClass:[CPArray class]]) - value = [value]; + value = [CPArray arrayWithObject:value]; var oldSelectedObjects = nil, oldSelectionIndexPaths = nil; @@ -264,25 +178,13 @@ [self _rearrangeObjects]; - if ([self preservesSelection]) - [self __setSelectedObjects:oldSelectedObjects]; + if ([self preservesSelection])[self __setSelectedObjects:oldSelectedObjects]; else[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection]; } -- (void)_setContentArray:(id)anArray -{ - [self setContent:anArray]; -} - -- (id)contentArray -{ - return [self content]; -} - -- (id)arrangedObjects -{ - return _arrangedObjects; -} +- (void)_setContentArray:(id)anArray {[self setContent:anArray]; } +- (id)contentArray { return [self content]; } +- (id)arrangedObjects { return _arrangedObjects; } - (void)rearrangeObjects { @@ -301,18 +203,16 @@ else oldSelectionIndexPaths = [self selectionIndexPaths]; - // Rebuild the proxy tree from the content using _childrenKeyPath and _sortDescriptors[self __rebuildArrangedObjectsTree]; + [self __rebuildArrangedObjectsTree]; - if ([self preservesSelection]) - [self __setSelectedObjects:oldSelectedObjects]; - else - [self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection]; + if ([self preservesSelection])[self __setSelectedObjects:oldSelectedObjects]; + else[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection]; } - (void)__rebuildArrangedObjectsTree { - var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil]; - var contentArray = [self contentArray]; + var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil], + contentArray = [self contentArray]; if (contentArray && [contentArray count] > 0) { @@ -323,22 +223,20 @@ _arrangedObjects = rootNode; } -// Recursively builds the node tree mapping model objects to CPTreeNodes - (CPArray)_buildTreeNodesForObjects:(CPArray)objects { var count = [objects count]; - if (count === 0) - return[]; + if (count === 0) return [CPArray array]; var sortedObjects = objects; - if ([_sortDescriptors count] > 0) + if (_sortDescriptors && [_sortDescriptors count] > 0) sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors]; var nodes = [CPMutableArray arrayWithCapacity:count]; for (var i = 0; i < count; i++) { - var obj = sortedObjects[i], + var obj = [sortedObjects objectAtIndex:i], node = [[CPTreeNode alloc] initWithRepresentedObject:obj]; if (_childrenKeyPath) @@ -349,16 +247,12 @@ var childNodes = [self _buildTreeNodesForObjects:childObjects]; [[node mutableChildNodes] addObjectsFromArray:childNodes]; } - } - - [nodes addObject:node]; + }[nodes addObject:node]; } return nodes; } -// --- Selection Management --- - - (CPIndexPath)selectionIndexPath { return [_selectionIndexPaths count] > 0 ? [_selectionIndexPaths objectAtIndex:0] : nil; @@ -366,20 +260,18 @@ - (BOOL)setSelectionIndexPath:(CPIndexPath)indexPath { - var paths = indexPath ? [indexPath] : []; - return [self setSelectionIndexPaths:paths]; + var paths = indexPath ? [CPArray arrayWithObject:indexPath] : [CPArray array]; + return[self setSelectionIndexPaths:paths]; } -- (CPArray)selectionIndexPaths -{ - return _selectionIndexPaths; -} +- (CPArray)selectionIndexPaths { return _selectionIndexPaths; } - (BOOL)setSelectionIndexPaths:(CPArray)indexPaths { [self _selectionWillChange]; var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO]; [self _selectionDidChange]; + return result; } @@ -388,12 +280,12 @@ var newPaths = indexPaths; if (!newPaths) - newPaths = []; + newPaths = [CPArray array]; if (![newPaths count] && avoidEmpty) { if ([[[self arrangedObjects] childNodes] count] > 0) - newPaths = [[CPIndexPath indexPathWithIndex:0]]; + newPaths = [CPArray arrayWithObject:[CPIndexPath indexPathWithIndex:0]]; } if ([_selectionIndexPaths isEqualToArray:newPaths]) @@ -402,7 +294,11 @@ _selectionIndexPaths = [newPaths copy]; var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"]; - [[binderClass getBinding:@"selectionIndexPaths" forObject:self] reverseSetValueFor:@"selectionIndexPaths"]; + if (binderClass) + { + var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self]; + if (binding)[binding reverseSetValueFor:@"selectionIndexPaths"]; + } return YES; } @@ -410,8 +306,9 @@ - (BOOL)addSelectionIndexPaths:(CPArray)indexPaths { var newPaths = [_selectionIndexPaths mutableCopy]; + [newPaths addObjectsFromArray:indexPaths]; - // Remove duplicates and maintain sorted order (omitted for brevity) + return [self setSelectionIndexPaths:newPaths]; } @@ -424,12 +321,12 @@ - (CPArray)selectedNodes { - var nodes = [], + var nodes = [CPMutableArray array], count = [_selectionIndexPaths count]; for (var i = 0; i < count; i++) { - var node = [[self arrangedObjects] descendantNodeAtIndexPath:_selectionIndexPaths[i]]; + var node = [[self arrangedObjects] descendantNodeAtIndexPath:[_selectionIndexPaths objectAtIndex:i]]; if (node) [nodes addObject:node]; } @@ -438,55 +335,70 @@ - (CPArray)selectedObjects { - var objects = [], + var objects = [CPMutableArray array], nodes = [self selectedNodes], count = [nodes count]; for (var i = 0; i < count; i++) - [objects addObject:[nodes[i] representedObject]]; + [objects addObject:[[nodes objectAtIndex:i] representedObject]]; return objects; } - (BOOL)__setSelectedObjects:(CPArray)objects { - // Search the tree for index paths matching the passed objects and update selection - // (Omitted recursive search for brevity) - return YES; + if (!objects || [objects count] === 0) + return[self __setSelectionIndexPaths:[CPArray array] avoidEmpty:_avoidsEmptySelection]; + + var newPaths = [CPMutableArray array]; + for (var i = 0, count = [objects count]; i < count; i++) + { + var path = [self _indexPathForObject:[objects objectAtIndex:i] inNode:[self arrangedObjects]]; + if (path) + [newPaths addObject:path]; + } + + return[self __setSelectionIndexPaths:newPaths avoidEmpty:_avoidsEmptySelection]; } -// --- Adding, Inserting, Removing --- - -- (BOOL)canInsert +- (CPIndexPath)_indexPathForObject:(id)anObject inNode:(CPTreeNode)node { - return[self isEditable]; + if ([node representedObject] === anObject && [node parentNode] != nil) + return [node indexPath]; + + var children = [node childNodes]; + if (children) + { + for (var i = 0, count = [children count]; i < count; i++) + { + var found = [self _indexPathForObject:anObject inNode:[children objectAtIndex:i]]; + if (found) + return found; + } + } + return nil; } -- (BOOL)canInsertChild -{ - return [self isEditable] && [_selectionIndexPaths count] > 0; -} - -- (BOOL)canAddChild -{ - return [self canInsertChild]; -} +- (BOOL)canInsert { return[self isEditable]; } +- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; } +- (BOOL)canAddChild { return [self canInsertChild]; } - (void)add:(id)sender { - if (![self canInsert]) - return; + if (![self canInsert]) return; - var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject]; + var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject], + selectionPath = [self selectionIndexPath]; - var selectionPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]]; + if (!selectionPath) + selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]]; - // Increment the last index by 1 to add *after* the current selection var length = [selectionPath length], - lastIndex = [selectionPath indexAtPosition:length - 1]; + lastIndex = [selectionPath indexAtPosition:length - 1], + insertPath = [selectionPath indexPathByRemovingLastIndex]; - var insertPath = [selectionPath indexPathByRemovingLastIndex]; insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1]; + [self insertObject:newObject atArrangedObjectIndexPath:insertPath]; } @@ -497,28 +409,25 @@ var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject], parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]], - childCount = [[parentNode childNodes] count]; - - var insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount]; + childCount = [[parentNode childNodes] count], + insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount]; [self insertObject:newObject atArrangedObjectIndexPath:insertPath]; } - (void)insert:(id)sender { - if (![self canInsert]) - return; + if (![self canInsert]) return; - var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject]; - var indexPath = [self selectionIndexPath] ||[CPIndexPath indexPathWithIndex:0]; + var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject], + indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0]; [self insertObject:newObject atArrangedObjectIndexPath:indexPath]; } - (void)insertChild:(id)sender { - if (![self canInsertChild]) - return; + if (![self canInsertChild]) return; var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject], insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0]; @@ -527,7 +436,8 @@ } - (void)insertObject:(id)anObject atArrangedObjectIndexPath:(CPIndexPath)indexPath -{[self insertObjects:[anObject] atArrangedObjectIndexPaths:[indexPath]]; +{ + [self insertObjects:[CPArray arrayWithObject:anObject] atArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]]; } - (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths @@ -537,35 +447,42 @@ var count = [objects count]; for (var i = 0; i < count; i++) { - var object = objects[i], - path = indexPaths[i], + var object = [objects objectAtIndex:i], + path = [indexPaths objectAtIndex:i], length = [path length]; if (length === 1) - { - // Insert at root level[_contentObject insertObject:object atIndex:[path indexAtPosition:0]]; + {[_contentObject insertObject:object atIndex:[path indexAtPosition:0]]; } else { - // Insert into a parent node's children var parentPath = [path indexPathByRemovingLastIndex], - parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath], - parentObj = [parentNode representedObject], - childIndex = [path indexAtPosition:length - 1]; + parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; - var children = [parentObj valueForKeyPath:_childrenKeyPath]; - if (!children) + if (parentNode) { - children = [CPMutableArray array]; - [parentObj setValue:children forKeyPath:_childrenKeyPath]; + var parentObj = [parentNode representedObject], + childIndex = [path indexAtPosition:length - 1]; + + var children = [parentObj valueForKeyPath:_childrenKeyPath]; + if (!children) + { + children = [CPMutableArray array]; + [parentObj setValue:children forKeyPath:_childrenKeyPath]; + } + + var mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath]; + + [mutableChildren insertObject:object atIndex:childIndex]; } - [children insertObject:object atIndex:childIndex]; } } - [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; - _disableSetContent = NO; + var binding = [[self class] _binderClassForBinding:@"contentArray"]; + if (binding) + [[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; + _disableSetContent = NO; [self _rearrangeObjects]; if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths]; @@ -574,12 +491,12 @@ } - (void)remove:(id)sender -{[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths]; +{ + [self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths]; } - (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath -{ - [self removeObjectsAtArrangedObjectIndexPaths:[indexPath]]; +{[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]]; } - (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths @@ -587,47 +504,50 @@ [self willChangeValueForKey:@"content"]; _disableSetContent = YES; - // Remove in reverse order to prevent shifting indices from invalidating remaining paths var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)], count = [sortedPaths count]; for (var i = count - 1; i >= 0; i--) { - var path = sortedPaths[i], + var path = [sortedPaths objectAtIndex:i], length = [path length]; if (length === 1) - { - [_contentObject removeObjectAtIndex:[path indexAtPosition:0]]; + {[_contentObject removeObjectAtIndex:[path indexAtPosition:0]]; } else { var parentPath = [path indexPathByRemovingLastIndex], - parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath], - parentObj = [parentNode representedObject], - childIndex = [path indexAtPosition:length - 1]; + parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; - var children = [parentObj valueForKeyPath:_childrenKeyPath]; - if (children) - [children removeObjectAtIndex:childIndex]; + if (parentNode) + { + var parentObj = [parentNode representedObject], + childIndex = [path indexAtPosition:length - 1], + mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath]; + + if (mutableChildren && childIndex <[mutableChildren count]) + [mutableChildren removeObjectAtIndex:childIndex]; + } } } - [[CPBinder getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; - _disableSetContent = NO; + var binding = [[self class] _binderClassForBinding:@"contentArray"]; + if (binding) + [[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange]; + _disableSetContent = NO; [self _rearrangeObjects]; [self didChangeValueForKey:@"content"]; } - (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath { - [self moveNodes:[node] toIndexPath:indexPath]; + [self moveNodes:[CPArray arrayWithObject:node] toIndexPath:indexPath]; } - (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath -{ - [CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."]; +{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."]; } @end @@ -663,7 +583,8 @@ CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath if (![self content] && [self automaticallyPreparesContent]) [self prepareContent]; - else if (![self content])[self _setContentArray:[]]; + else if (![self content]) + [self _setContentArray:[CPArray array]]; } return self; diff --git a/Tests/AppKit/CPTreeControllerTest.j b/Tests/AppKit/CPTreeControllerTest.j index 4d2f3fd3f..0538de760 100644 --- a/Tests/AppKit/CPTreeControllerTest.j +++ b/Tests/AppKit/CPTreeControllerTest.j @@ -1,7 +1,7 @@ /* * CPTreeControllerTest.j * - * Test suite for CPTreeController, adapted for Cappuccino + * Test suite for CPTreeController */ @import @@ -23,20 +23,17 @@ - (CPArray)makeTestTree { - // Root Level var engineering = [OrgNode nodeWithName:@"Engineering"], - marketing =[OrgNode nodeWithName:@"Marketing"]; + marketing = [OrgNode nodeWithName:@"Marketing"]; - // Children of Engineering var webTeam = [OrgNode nodeWithName:@"Web Team"], - backendTeam = [OrgNode nodeWithName:@"Backend Team"]; - + backendTeam = [OrgNode nodeWithName:@"Backend Team"]; + [engineering setChildren:[CPMutableArray arrayWithObjects:webTeam, backendTeam]]; - // Children of Web Team - var dev1 =[OrgNode nodeWithName:@"Francisco"], - dev2 = [OrgNode nodeWithName:@"Ross"]; - + var dev1 = [OrgNode nodeWithName:@"Francisco"], + dev2 = [OrgNode nodeWithName:@"Ross"]; + [webTeam setChildren:[CPMutableArray arrayWithObjects:dev1, dev2]]; return [CPMutableArray arrayWithObjects:engineering, marketing]; @@ -44,23 +41,25 @@ - (void)setUp { - // Init global CPApp used internally in AppKit [[CPApplication alloc] init]; _contentArray = [self makeTestTree]; _treeController = [[CPTreeController alloc] init]; - [_treeController setChildrenKeyPath:@"children"];[_treeController setContent:[_contentArray copy]]; + [_treeController setChildrenKeyPath:@"children"]; + [_treeController setContent:[_contentArray copy]]; } - (void)testInitWithContent -{[self assert:[_contentArray count] equals:[[_treeController contentArray] count]]; +{ + [self assert:[_contentArray count] equals:[[_treeController contentArray] count]]; [self assert:[CPTreeNode class] equals:[[[self treeController] arrangedObjects] class] message:@"arranged objects should be a proxy CPTreeNode root"]; } - (void)testInitWithoutContent { var emptyController = [[CPTreeController alloc] init]; - [self assert:[] equals:[emptyController contentArray]];[self assert:0 equals:[[[emptyController arrangedObjects] childNodes] count]]; + [self assert:[CPArray array] equals:[emptyController contentArray]]; + [self assert:0 equals:[[[emptyController arrangedObjects] childNodes] count]]; } - (void)testSetContent @@ -75,54 +74,50 @@ - (void)testSelectionPaths { var controller = [self treeController]; - - // Select Engineering -> Web Team (Index Path: [0, 0]) - var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];[controller setSelectionIndexPath:path]; - - var selectedPath = [controller selectionIndexPath];[self assert:path equals:selectedPath]; - + + var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]; + [controller setSelectionIndexPath:path]; + + var selectedPath = [controller selectionIndexPath]; + [self assert:path equals:selectedPath]; + var selectedNodes = [controller selectedNodes]; [self assert:1 equals:[selectedNodes count]]; - [self assert:@"Web Team" equals:[[selectedNodes[0] representedObject] name]]; + [self assert:@"Web Team" equals:[[[selectedNodes objectAtIndex:0] representedObject] name]]; } - (void)testAddChild { var controller = [self treeController]; [controller setObjectClass:[OrgNode class]]; - - // Select Engineering -> Backend Team (Index Path:[0, 1]) + var parentPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:1]; [controller setSelectionIndexPath:parentPath]; - - // Insert a child into Backend Team + var newDev = [OrgNode nodeWithName:@"Tom"]; - var insertPath =[parentPath indexPathByAddingIndex:0]; - + var insertPath = [parentPath indexPathByAddingIndex:0]; [controller insertObject:newDev atArrangedObjectIndexPath:insertPath]; - - // Validate it was added to the content + var engineering = [[controller contentArray] objectAtIndex:0], - backendTeam = [[engineering children] objectAtIndex:1]; - - [self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"];[self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]]; + backendTeam = [[engineering children] objectAtIndex:1]; + + [self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"]; + [self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]]; } - (void)testInsertObjectAtArrangedObjectIndexPath { var controller = [self treeController]; - - // Insert at root level, index 1 (between Engineering and Marketing) - var path =[CPIndexPath indexPathWithIndex:1]; - var hrDept = [OrgNode nodeWithName:@"Human Resources"];[controller insertObject:hrDept atArrangedObjectIndexPath:path]; - + + var path = [CPIndexPath indexPathWithIndex:1]; + var hrDept = [OrgNode nodeWithName:@"Human Resources"]; + [controller insertObject:hrDept atArrangedObjectIndexPath:path]; [self assert:3 equals:[[controller contentArray] count]]; [self assert:hrDept equals:[[controller contentArray] objectAtIndex:1]]; - - // Insert nested (Engineering -> HR) + var nestedPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]; - var subDept =[OrgNode nodeWithName:@"Sub Dept"]; - + var subDept = [OrgNode nodeWithName:@"Sub Dept"]; + [controller insertObject:subDept atArrangedObjectIndexPath:nestedPath]; var engChildren = [[[controller contentArray] objectAtIndex:0] children]; [self assert:subDept equals:[engChildren objectAtIndex:0] message:@"Object should be inserted at the correct nested index path"]; @@ -131,61 +126,56 @@ - (void)testRemoveObjectAtArrangedObjectIndexPath { var controller = [self treeController]; - - // Remove Engineering -> Web Team -> Francisco (Index Path: [0, 0, 0]) + var path = [[[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0] indexPathByAddingIndex:0]; - + [controller removeObjectAtArrangedObjectIndexPath:path]; - + var engineering = [[controller contentArray] objectAtIndex:0], - webTeam = [[engineering children] objectAtIndex:0];[self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"]; + webTeam = [[engineering children] objectAtIndex:0]; + + [self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"]; [self assert:@"Ross" equals:[[[webTeam children] objectAtIndex:0] name]]; } - (void)testRemoveObjectsAtArrangedObjectIndexPaths { var controller = [self treeController]; - - // Remove both Engineering (0) and Marketing (1) - var paths = [[CPIndexPath indexPathWithIndex:0], - [CPIndexPath indexPathWithIndex:1] - ]; - + + var paths = [CPArray arrayWithObjects:[CPIndexPath indexPathWithIndex:0],[CPIndexPath indexPathWithIndex:1]]; + [controller removeObjectsAtArrangedObjectIndexPaths:paths]; - + [self assert:0 equals:[[controller contentArray] count] message:@"All root nodes should be removed"]; } - (void)testAvoidsEmptySelection { - var controller = [self treeController];[controller setAvoidsEmptySelection:YES]; - - // Set empty selection manually - [controller setSelectionIndexPaths:[]]; - + var controller = [self treeController]; + [controller setAvoidsEmptySelection:YES]; + [controller setSelectionIndexPaths:[CPArray array]]; + [self assertTrue:([[controller selectionIndexPaths] count] == 1) message:@"Selection should fallback to the first item when avoidsEmptySelection is YES"]; [self assert:[CPIndexPath indexPathWithIndex:0] equals:[controller selectionIndexPath]]; - - [controller setAvoidsEmptySelection:NO];[controller setSelectionIndexPaths:[]]; - + [controller setAvoidsEmptySelection:NO]; + [controller setSelectionIndexPaths:[CPArray array]]; [self assertTrue:([[controller selectionIndexPaths] count] == 0) message:@"Selection should be allowed to be empty when avoidsEmptySelection is NO"]; } - (void)testChildrenKeyPathOverride { var controller = [[CPTreeController alloc] init]; - // Use a custom key path [controller setChildrenKeyPath:@"subItems"]; - - var data =[OrgNode nodeWithName:@"Root"]; + + var data = [OrgNode nodeWithName:@"Root"]; [data setValue:[CPMutableArray arrayWithObject:[OrgNode nodeWithName:@"Sub"]] forKey:@"subItems"]; - + [controller setContent:[CPMutableArray arrayWithObject:data]]; - - // Insert at [0, 0] + var path = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]; - var newItem = [OrgNode nodeWithName:@"New Sub"];[controller insertObject:newItem atArrangedObjectIndexPath:path]; - + var newItem = [OrgNode nodeWithName:@"New Sub"]; + [controller insertObject:newItem atArrangedObjectIndexPath:path]; + var subs = [data valueForKey:@"subItems"]; [self assert:2 equals:[subs count]]; [self assert:newItem equals:[subs objectAtIndex:0] message:@"Object should be inserted using the custom childrenKeyPath"]; @@ -193,36 +183,36 @@ - (void)testContentBinding { - var controller = [[CPTreeController alloc] init];[controller bind:@"contentArray" toObject:self withKeyPath:@"contentArray" options:nil]; + var controller = [[CPTreeController alloc] init]; + + [controller bind:@"contentArray" toObject:self withKeyPath:@"contentArray" options:nil]; [self assert:[self contentArray] equals:[controller contentArray]]; - - // Verify proxy tree rebuilt [self assert:2 equals:[[[controller arrangedObjects] childNodes] count]]; } - (void)testSelectedObjects { - var controller =[self treeController]; - - // Select Marketing [1] - var path =[CPIndexPath indexPathWithIndex:1]; + var controller = [self treeController]; + + var path = [CPIndexPath indexPathWithIndex:1]; [controller setSelectionIndexPath:path]; - - var selectedObjects =[controller selectedObjects]; - [self assert:1 equals:[selectedObjects count]];[self assert:@"Marketing" equals:[selectedObjects[0] name]]; + + var selectedObjects = [controller selectedObjects]; + [self assert:1 equals:[selectedObjects count]]; + [self assert:@"Marketing" equals:[[selectedObjects objectAtIndex:0] name]]; } @end -/* +/* * Dummy Model Class for Testing */ @implementation OrgNode : CPObject { CPString _name @accessors(property=name); CPMutableArray _children @accessors(property=children); - CPMutableArray _subItems @accessors(property=subItems); // For testing custom key paths + CPMutableArray _subItems @accessors(property=subItems); } + (id)nodeWithName:(CPString)aName @@ -236,7 +226,7 @@ { _name = aName; _children = [CPMutableArray array]; - _subItems =[CPMutableArray array]; + _subItems = [CPMutableArray array]; } return self; @@ -244,7 +234,8 @@ - (CPString)description { - return [CPString stringWithFormat:@"", [self name]]; + return[CPString stringWithFormat:@"", [self name]]; } -@end \ No newline at end of file +@end +