mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-09 18:30:59 +00:00
New implementation of change notifications from CPArrayController.
It is crucial that we send notification only before and after complete changes. In the middle notifications might cause observers to see or react to inconsistent data (e.g. selection indexes pointing to rows no longer present). Added some unit tests - more might be needed in the future. Fixed: before and after values when observing array controller key paths during content changes or rearranges were wrong. Fixed: the array controller sent out multiple redundant change notifications.
This commit is contained in:
1 parent
a3d89c8d78
commit
febdf4092e
3 files changed
+275
-93
No files matched your search
@@ -1,7 +1,9 @@
|
||||
@implementation CPArrayControllerTest : OJTestCase
|
||||
{
|
||||
CPArrayController _arrayController @accessors(property=arrayController);
|
||||
CPArray _contentArray @accessors(property=contentArray);
|
||||
CPArrayController _arrayController @accessors(property=arrayController);
|
||||
CPArray _contentArray @accessors(property=contentArray);
|
||||
|
||||
CPArray observations;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
@@ -19,7 +21,7 @@
|
||||
- (void)testInitWithContent
|
||||
{
|
||||
[self assert:[self contentArray] equals:[[self arrayController] contentArray]];
|
||||
[self assert:[_CPObservableArray class] equals:[[[self arrayController] arrangedObjects] class]];
|
||||
[self assert:[_CPObservableArray class] equals:[[[self arrayController] arrangedObjects] class] message:"arranged objects should be observable"];
|
||||
}
|
||||
|
||||
- (void)testSetContent
|
||||
@@ -125,12 +127,126 @@
|
||||
[self assert:[[self arrayController] contentArray] equals:[self contentArray]];
|
||||
}
|
||||
|
||||
- (CPArray)setupObservationFixture
|
||||
{
|
||||
// objj_msgSend_decorate(objj_backtrace_decorator);
|
||||
|
||||
var ac = [self arrayController];
|
||||
[ac setSelectionIndex:0];
|
||||
[ac setPreservesSelection:YES];
|
||||
var newContent = [_contentArray copy];
|
||||
[newContent removeObjectAtIndex:0];
|
||||
|
||||
[ac addObserver:self forKeyPath:"content" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
[ac addObserver:self forKeyPath:"selectionIndexes" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
[ac addObserver:self forKeyPath:"selectedObjects" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
[ac addObserver:self forKeyPath:"arrangedObjects" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:nil];
|
||||
|
||||
observations = [];
|
||||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
- (void)testObservationDuringSetContent
|
||||
{
|
||||
/*
|
||||
There are two areas where CPArrayController can get into trouble with observation.
|
||||
|
||||
The more serious one is that the before values when observing selectedObjects
|
||||
need to be correct and not affected by the new setContent: values.
|
||||
|
||||
Second, the array controller should not send out more than one of each notification
|
||||
since repeated notifications for the same change can be a huge performance drain
|
||||
from such a central piece of code.
|
||||
*/
|
||||
|
||||
var ac = [self arrayController],
|
||||
newContent = [self setupObservationFixture];
|
||||
|
||||
[ac setContent:newContent];
|
||||
|
||||
[observations sortUsingFunction:function(a, b) { return [a.keyPath compare:b.keyPath] } context:nil];
|
||||
[self assert:4 equals:[observations count] message:"exactly 4 change notifications should be sent for new content"];
|
||||
|
||||
// for (var i=0; i<observations.length; i++)
|
||||
// CPLog.error(observations[i].keyPath);
|
||||
|
||||
var observation = observations[0];
|
||||
[self assert:"arrangedObjects" equals:observation.keyPath];
|
||||
[self assert:_contentArray equals:observation.oldValue message:"old arranged content should be correct"];
|
||||
[self assert:newContent equals:observation.newValue message:"new arranged content should be correct"];
|
||||
|
||||
observation = observations[1];
|
||||
[self assert:"content" equals:observation.keyPath];
|
||||
[self assert:_contentArray equals:observation.oldValue message:"old content should be correct"];
|
||||
[self assert:newContent equals:observation.newValue message:"new content should be correct"];
|
||||
|
||||
observation = observations[2];
|
||||
[self assert:"selectedObjects" equals:observation.keyPath];
|
||||
[self assert:[_contentArray[0]] equals:observation.oldValue message:"old selected objects should be previous object at selected index"];
|
||||
[self assert:[] equals:observation.newValue message:"new selected objects should be nothing"];
|
||||
|
||||
observation = observations[3];
|
||||
[self assert:"selectionIndexes" equals:observation.keyPath];
|
||||
[self assert:[CPIndexSet indexSetWithIndex:0] equals:observation.oldValue message:"old selected index should be 0"];
|
||||
[self assert:[CPIndexSet indexSet] equals:observation.newValue message:"new selected index should be nothing"];
|
||||
}
|
||||
|
||||
- (void)testObservationDuringRearrange
|
||||
{
|
||||
var ac = [self arrayController],
|
||||
newContent = [self setupObservationFixture],
|
||||
selection = [CPIndexSet indexSetWithIndex:0];
|
||||
|
||||
[selection addIndex:1];
|
||||
// Francisco and Ross selected.
|
||||
[ac setSelectionIndexes:selection];
|
||||
|
||||
observations = [];
|
||||
[ac setFilterPredicate:[CPPredicate predicateWithFormat:@"(name != %@)", "Francisco"]];
|
||||
|
||||
[observations sortUsingFunction:function(a, b) { return [a.keyPath compare:b.keyPath] } context:nil];
|
||||
//for (var i=0; i<observations.length; i++)
|
||||
// CPLog.error(observations[i].keyPath);
|
||||
[self assert:3 equals:[observations count] message:"exactly 3 change notifications should be sent for new filter"];
|
||||
|
||||
var observation = observations[0];
|
||||
[self assert:"arrangedObjects" equals:observation.keyPath];
|
||||
[self assert:_contentArray equals:observation.oldValue message:"old arranged content should be correct"];
|
||||
[self assert:newContent equals:observation.newValue message:"new arranged content should be correct"];
|
||||
|
||||
observation = observations[1];
|
||||
[self assert:"selectedObjects" equals:observation.keyPath];
|
||||
[self assert:[_contentArray[0], _contentArray[1]] equals:observation.oldValue message:"old selected objects should be previous object at selected index"];
|
||||
[self assert:[_contentArray[1]] equals:observation.newValue message:"new selected objects should be only non filtered ones"];
|
||||
|
||||
observation = observations[2];
|
||||
[self assert:"selectionIndexes" equals:observation.keyPath];
|
||||
[self assert:selection equals:observation.oldValue message:"old selected index should be 0 and 1"];
|
||||
[self assert:[CPIndexSet indexSetWithIndex:0] equals:observation.newValue message:"new selected index should be 0"];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:keyPath
|
||||
ofObject:anActivity
|
||||
change:change
|
||||
context:context
|
||||
{
|
||||
// CPLog.warn("observeValueForKeyPath:" + keyPath);
|
||||
// objj_backtrace_print(CPLog.error);
|
||||
|
||||
[observations addObject:{
|
||||
keyPath: keyPath,
|
||||
oldValue: [change valueForKey:CPKeyValueChangeOldKey],
|
||||
newValue: [change valueForKey:CPKeyValueChangeNewKey]
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation Person : CPObject
|
||||
{
|
||||
CPString _name @accessors(property=name);
|
||||
int _age @accessors(property=age);
|
||||
CPString _name @accessors(property=name);
|
||||
int _age @accessors(property=age);
|
||||
}
|
||||
|
||||
+ (id)personWithName:(CPString)aName age:(int)anAge
|
||||
@@ -151,7 +267,7 @@
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"%@ : %@", [self name], [self age]];
|
||||
return [CPString stringWithFormat:@"<Person %@ : %@>", [self name], [self age]];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user