libs-base/Tests/base/NSKVOSupport/userdefaults.m

174 lines
6.4 KiB
Mathematica
Raw Normal View History

Initial port of WinObjC's KVO implementation to GNUstep (#420) * GSAtomic: Add prefix to macro definitions * NSKVOSupport: Import * NSKVOSupport: Add test cases * NSKVOSwizzling: Ugly C Rewrite * NSKeyValueObserving: Use old implementation as fallback * NSKeyValueObserving: Rename TypeEncodingCases header * NSKVOSupport: Fix new objects not being added to NSKeyValueChangeNew set on set mutation * NSKeyValueMutableSet: Fix will and didChange notifications for set operations * NSKeyValueMutableSet: Document Accessor Search Patterns * NSKVOSupport: Add toMany test * NSKeyValueCoding: Change notifications when changing value via setValue:forKey: * NSKVOSupport: Add more tests * NSKVOSupport: Do not wrap block in try/finally to avoid crash in windows * NSKVOSwizzling: use _alloca on Windows * NSKVOSupport: Do not autorelease newWithObservee: * NSKVOSupport: Do not leak Observee and TestFacade objects * Improve runtime detection in makefile * Add file extension of source file in GNUMakefile * NSKVOSupport: Remove @status comments * NSKVOSupport: Implement private notify method * NSUserDefaults: KVO Support and fix macOS incompatibilities * NSKeyValueObserving: Set old to null if nil * NSKeyValueObserving: Remove cached new value * NSMethodSignature: Add signature cache * NSKVOSupport: Remove ObjC2 features and mark tests failing on GCC as hopeful * Call class method instead of private _keyPathsForValuesAffectingValueForKey * Move _keyPathsForValuesAffectingValueForKey body into class method and statically construct empty NSSet * NSUserDefaults: Change notification should contain old value from other domains aswell * NSUserDefaults: Fetch new value from all domains * NSKVOInternal: Fixup filename in header * NSUserDefaults: Go through search list instead of only one domain in KVO change * Making indentation a bit less worse * Add NSUserDefaults KVO tests * NSKVOSupport: NSUserDefaults test small fixes * Add autoreleasepool * NSUserDefaults: Only emit change notifications if value changed * Avoid compiler warnings and tidy some of the whitespace/formatting --------- Co-authored-by: Frederik Seiffert <frederik@algoriddim.com> Co-authored-by: rfm <richardfrithmacdonald@gmail.com> Co-authored-by: rfm <rfm@gnu.org>
2024-11-10 08:05:23 -08:00
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSNull.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSKeyValueObserving.h>
#import <Testing.h>
/* NSUserDefaults KeyValueObserving Tests
*
* Behaviour was validated on macOS 15.0.1 (24A348)
*/
@interface Observer : NSObject
{
@public
NSInteger called;
NSString *lastKeyPath;
id lastObject;
NSDictionary *lastChange;
}
@end
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
called++;
ASSIGN(lastKeyPath, keyPath);
ASSIGN(lastObject, object);
ASSIGN(lastChange, change);
}
- (void)dealloc
{
RELEASE(lastKeyPath);
RELEASE(lastObject);
RELEASE(lastChange);
[super dealloc];
}
@end
// NSUserDefaults Domain Search List:
// NSArgumentDomain
// Application Domain
// NSGlobalDomain
// NSRegistrationDomain
//
// Terminology:
// - Entry: An entry is a key value pair.
// - Object and Value: both used interchangeably in the NSUserDefaults API to
// describe the value associated with a given key.
//
// Note that -removeObjectForKey: and -setObject:ForKey: emit only a
// KVO notification when the value has actually changed, meaning
// -objectForKey: would return a different value than before.
//
// Example:
// Assume that a key with the same value is registered in both NSArgumentDomain
// and the application domain. If we remove the value with -removeObjectForKey:,
// we set the value for the key in the application domain to nil, but we stil
// have an entry in the NSArgumentDomain. Thus -objectForKey will return the
// same value as before and no change notification is emitted.
int
main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
Observer *obs = [Observer new];
NSString *key1 = @"key1";
NSString *value1 = @"value1";
NSString *key2 = @"key2";
NSString *value2 = @"value2";
NSString *value2Alt = @"value2Alt";
[defs addObserver:obs
forKeyPath:key1
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
[defs addObserver:obs
forKeyPath:key2
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
// Check if we receive KVO notifications when setting default key in the
// standard application domain
[defs setObject:value1 forKey:key1];
PASS(obs->called == 1, "KVO notification received");
PASS(obs->lastObject != nil, "object is not nil");
PASS(obs->lastChange != nil, "change is not nil");
PASS_EQUAL([obs->lastChange objectForKey:@"kind"],
[NSNumber numberWithInteger:1], "value for 'kind' is 1");
PASS_EQUAL([obs->lastChange objectForKey:@"old"], [NSNull null],
"value for 'old' is [NSNull null]");
PASS_EQUAL([obs->lastChange objectForKey:@"new"], value1,
"value for 'new' is 'value1'");
[defs removeObjectForKey:key1];
PASS(obs->called == 2, "KVO notification received");
PASS(obs->lastObject != nil, "object is not nil");
PASS(obs->lastChange != nil, "change is not nil");
PASS_EQUAL([obs->lastChange objectForKey:@"kind"],
[NSNumber numberWithInteger:1], "value for 'kind' is 1");
PASS_EQUAL([obs->lastChange objectForKey:@"old"], value1,
"value for 'old' is value1");
PASS_EQUAL([obs->lastChange objectForKey:@"new"], [NSNull null],
"value for 'new' is [NSNull null]");
// Test setting two different values for the same key in application domain
// and registration domain. When removing the value in the application domain,
// the value for 'new' in the change dictionary is not nil, but rather the
// value from the registration domain.
[defs setObject:value2 forKey:key2];
PASS(obs->called == 3, "KVO notification received");
PASS(obs->lastObject != nil, "object is not nil");
PASS(obs->lastChange != nil, "change is not nil");
PASS_EQUAL([obs->lastChange objectForKey:@"kind"],
[NSNumber numberWithInteger:1], "value for 'kind' is 1");
PASS_EQUAL([obs->lastChange objectForKey:@"old"], [NSNull null],
"value for 'old' is [NSNull null]");
PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2,
"value for 'new' is 'value2'");
// Set default key in registration domain that is _different_ to the key
// registered in the application domain. This will trigger a change
// notification, when the entry is removed from the application domain.
NSDictionary *registrationDict = [NSDictionary dictionaryWithObject:value2Alt
forKey:key2];
[defs registerDefaults:registrationDict];
[defs removeObjectForKey:key2];
PASS(obs->called == 4, "KVO notification received");
PASS(obs->lastObject != nil, "object is not nil");
PASS(obs->lastChange != nil, "change is not nil");
PASS_EQUAL([obs->lastChange objectForKey:@"kind"],
[NSNumber numberWithInteger:1], "value for 'kind' is 1");
PASS_EQUAL([obs->lastChange objectForKey:@"old"], value2,
"value for 'old' is value2");
// this must not be null in this case
PASS_EQUAL([obs->lastChange objectForKey:@"new"], value2Alt,
"value for 'new' is 'value2Alt'");
// Set default key in registration domain that is _equal_ to the key
// registered in the application domain. This will _not_ trigger a change
// notification, when the entry is removed from the application domain.
registrationDict = [NSDictionary dictionaryWithObject:value1 forKey:key1];
[defs registerDefaults:registrationDict];
// Does not emit a KVO notification as value is not changed
[defs setObject:value1 forKey:key1];
PASS(obs->called == 4,
"KVO notification was not emitted as other domain has the same entry");
// Remove the entry from the application domain.
[defs removeObjectForKey:key1];
PASS(obs->called == 4,
"KVO notification was not emitted as other domain has the same entry");
[defs removeObserver:obs forKeyPath:key1];
[defs removeObserver:obs forKeyPath:key2];
[pool drain];
[obs release];
return 0;
}