From df3264de942cf45752ebef0e26507634aa09e392 Mon Sep 17 00:00:00 2001 From: Ross Boucher Date: Wed, 24 Sep 2008 13:36:25 -0700 Subject: [PATCH] Updates to KVO and KVO tests. --- Foundation/CPKeyValueObserving.j | 165 +++++++++++++++++++++++-------- Tests/Foundation/CPKVOTest.j | 143 ++++++++++++++++++++++++--- 2 files changed, 253 insertions(+), 55 deletions(-) diff --git a/Foundation/CPKeyValueObserving.j b/Foundation/CPKeyValueObserving.j index 6b16584bc..8870225cd 100644 --- a/Foundation/CPKeyValueObserving.j +++ b/Foundation/CPKeyValueObserving.j @@ -31,7 +31,6 @@ import "CPObject.j" } - - (void)didChangeValueForKey:(CPString)aKey { @@ -60,15 +59,31 @@ import "CPObject.j" @end +// KVO Options +CPKeyValueObservingOptionNew = 1 << 0; +CPKeyValueObservingOptionOld = 1 << 1; +CPKeyValueObservingOptionInitial = 1 << 2; +CPKeyValueObservingOptionPrior = 1 << 3; + +// KVO Change Dictionary Keys +CPKeyValueChangeKindKey = @"CPKeyValueChangeKindKey"; +CPKeyValueChangeNewKey = @"CPKeyValueChangeNewKey"; +CPKeyValueChangeOldKey = @"CPKeyValueChangeOldKey"; +CPKeyValueChangeIndexesKey = @"CPKeyValueChangeIndexesKey"; +CPKeyValueChangeNotificationIsPriorKey = @"CPKeyValueChangeNotificationIsPriorKey"; + +// Map of real objects to their KVO proxy var KVOProxyMap = [CPDictionary dictionary]; //rule of thumb: _ methods are called on the real proxy object, others are called on the "fake" proxy object (aka the real object) +/* @ignore */ @implementation _CPKVOProxy : CPObject { id _targetObject; Class _nativeClass; - CPDictionary _observerInfos; + CPDictionary _changesForKey; + CPDictionary _observersForKey; CPDictionary _replacementMethods; } @@ -96,8 +111,9 @@ var KVOProxyMap = [CPDictionary dictionary]; _targetObject = aTarget; _nativeClass = [aTarget class]; - _observerInfos = [CPDictionary dictionary]; _replacementMethods = [CPDictionary dictionary]; + _observersForKey = [CPDictionary dictionary]; + _changesForKey = [CPDictionary dictionary]; return self; } @@ -113,7 +129,7 @@ var KVOProxyMap = [CPDictionary dictionary]; for (var i=0; i import @implementation CPKVOTest : OJTestCase +{ + BOOL _sawInitialObservation; + BOOL _sawPriorObservation; +} - (void)testAddObserver { var bob = [[PersonTester alloc] init]; - [bob addObserver:[CPObject new] forKeyPath:@"name" options:nil context:nil]; + [bob addObserver:self forKeyPath:@"name" options:nil context:"testAddObserver"]; [bob setValue:@"bob" forKey:@"name"]; @@ -19,8 +23,8 @@ import { var bob = [[PersonTester alloc] init]; - [bob addObserver:[CPArray new] forKeyPath:@"name" options:nil context:nil]; - [bob addObserver:[CPString new] forKeyPath:@"name" options:nil context:nil]; + [bob addObserver:self forKeyPath:@"name" options:nil context:"testAddTwoObservers"]; + [bob addObserver:[CPObject new] forKeyPath:@"name" options:nil context:"testAddTwoObservers"]; [bob setValue:@"bob" forKey:@"name"]; @@ -32,7 +36,7 @@ import { var bob = [[PersonTester alloc] init]; - [bob addObserver:[CPObject new] forKeyPath:@"phoneNumber" options:nil context:nil]; + [bob addObserver:self forKeyPath:@"phoneNumber" options:nil context:"testDirectIVarObservation"]; [bob setValue:@"555" forKey:@"phoneNumber"]; @@ -42,11 +46,25 @@ import - (void)testRemoveObserver { - var bob = [[PersonTester alloc] init], - obj = [CPArray new]; + var bob = [[PersonTester alloc] init]; - [bob addObserver:obj forKeyPath:@"name" options:nil context:nil]; - [bob addObserver:[CPString new] forKeyPath:@"name" options:nil context:nil]; + [bob addObserver:self forKeyPath:@"name" options:nil context:"testRemoveObserver"]; + [bob addObserver:[CPString new] forKeyPath:@"name" options:nil context:"testRemoveObserver"]; + + [bob removeObserver:self forKeyPath:@"name"]; + + [bob setValue:@"bob" forKey:@"name"]; + + [self assertTrue: [bob valueForKey:@"name"] == @"set_bob" message: "valueForKey:'name' should be bob, was: "+[bob valueForKey:@"name"]]; +} + +- (void)testRemoveOtherObserver +{ + var bob = [[PersonTester alloc] init], + obj = [CPString new]; + + [bob addObserver:self forKeyPath:@"name" options:nil context:"testRemoveOtherObserver"]; + [bob addObserver:obj forKeyPath:@"name" options:nil context:"testRemoveOtherObserver"]; [bob removeObserver:obj forKeyPath:@"name"]; @@ -61,8 +79,8 @@ import obj1 = [CPArray new], obj2 = [CPString new]; - [bob addObserver:obj1 forKeyPath:@"name" options:nil context:nil]; - [bob addObserver:obj2 forKeyPath:@"name" options:nil context:nil]; + [bob addObserver:obj1 forKeyPath:@"name" options:nil context:"testRemoveAllObservers"]; + [bob addObserver:obj2 forKeyPath:@"name" options:nil context:"testRemoveAllObservers"]; [bob removeObserver:obj1 forKeyPath:@"name"]; [bob removeObserver:obj2 forKeyPath:@"name"]; @@ -74,8 +92,109 @@ import } -//test actual observing -//test each observing option +- (void)testPriorObservationOption +{ + _sawPriorObservation = NO; + + var bob = [[PersonTester alloc] init]; + + [bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionPrior context:"testPriorObservationOption"]; + + [bob setValue:@"bob" forKey:@"name"]; + + [self assertTrue: _sawPriorObservation message: "asked for CPKeyValueObservingOptionPrior but did not recieve corresponding notification"]; +} + +- (void)testInitialObservationOption +{ + _sawInitialObservation = NO; + + var bob = [[PersonTester alloc] init]; + + bob.name = "paul"; + + [bob addObserver:self forKeyPath:@"name" options:CPKeyValueObservingOptionInitial context:"testInitialObservationOption"]; + [bob removeObserver:self forKeyPath:@"name"]; + + [bob setValue:@"bob" forKey:@"name"]; + + [self assertTrue: _sawInitialObservation message: "asked for CPKeyValueObservingOptionInitial but did not recieve corresponding notification"]; +} + +- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext +{ + var oldValue = [changes objectForKey:CPKeyValueChangeOldKey], + newValue = [changes objectForKey:CPKeyValueChangeNewKey]; + + switch (aContext) + { + case "testAddObserver": + [self assertTrue: newValue == "set_bob" message: "newValue should be: set_bob was: "+newValue]; + [self assertTrue: oldValue == [CPNull null] message: "oldValue should be CPNull was: "+oldValue]; + break; + + case "testAddTwoObservers": + [self assertTrue: newValue == "set_bob" message: "newValue should be: set_bob was: "+newValue]; + [self assertTrue: oldValue == [CPNull null] message: "oldValue should be CPNull was: "+oldValue]; + break; + + case "testDirectIVarObservation": + [self assertTrue: newValue == "555" message: "newValue should be: 555 was: "+newValue]; + [self assertTrue: oldValue == [CPNull null] message: "oldValue should be CPNull was: "+oldValue]; + break; + + case "testRemoveObserver": + [self assertTrue: NO message: "observer was removed, but notification was still received"]; + break; + + case "testRemoveOtherObserver": + [self assertTrue: newValue == "set_bob" message: "newValue should be: set_bob was: "+newValue]; + [self assertTrue: oldValue == [CPNull null] message: "oldValue should be CPNull was: "+oldValue]; + break; + + case "testRemoveAllObservers": + [self assertTrue: NO message: "all observers were removed, but notification was still received"]; + break; + + case "testPriorObservationOption": + var prior = [changes objectForKey:CPKeyValueChangeNotificationIsPriorKey]; + + if (!_sawPriorObservation) + { + [self assertTrue:prior message:"Have not been sent the prior notification, but it should have been sent"]; + [self assertTrue:oldValue == [CPNull null] message: "Shoudl be no initial value"]; + [self assertFalse:newValue message: "Should be no object for the new value key on the prior notification"]; + _sawPriorObservation = YES; + } + else + { + [self assertFalse: prior message: "there should be no value for the notification is prior key, that notification was already sent"]; + [self assertTrue: newValue == "set_bob" message: "newValue should be: set_bob was: "+newValue]; + } + break; + + case "testInitialObservationOption": + if (!_sawInitialObservation) + { + [self assertTrue: newValue == "paul" message:"Expected old value to be: paul was: "+oldValue]; + [self assertFalse: oldValue message:"Should be no value for new change key on initial observation"]; + _sawInitialObservation = YES; + } + else + [self assertFalse:YES message:"Should never have received this notification"]; + + break; + } +} + +@end + +@implementation CPObject (KVO) + +- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)aContext +{ + +} @end