aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting/GTMNSObject+BindingUnitTesting.m
diff options
context:
space:
mode:
Diffstat (limited to 'UnitTesting/GTMNSObject+BindingUnitTesting.m')
-rw-r--r--UnitTesting/GTMNSObject+BindingUnitTesting.m440
1 files changed, 440 insertions, 0 deletions
diff --git a/UnitTesting/GTMNSObject+BindingUnitTesting.m b/UnitTesting/GTMNSObject+BindingUnitTesting.m
new file mode 100644
index 0000000..03d723d
--- /dev/null
+++ b/UnitTesting/GTMNSObject+BindingUnitTesting.m
@@ -0,0 +1,440 @@
+//
+// GTMNSObject+BindingUnitTesting.m
+//
+// An informal protocol for doing advanced binding unittesting with objects.
+//
+// Copyright 2006-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMDefines.h"
+#import "GTMNSObject+BindingUnitTesting.h"
+
+BOOL GTMDoExposedBindingsFunctionCorrectly(NSObject *object,
+ NSArray **errors) {
+ NSMutableArray *errorArray = [NSMutableArray array];
+ if (errors) {
+ *errors = nil;
+ }
+ NSArray *bindings = [object exposedBindings];
+ if ([bindings count]) {
+ NSArray *bindingsToIgnore = [object gtm_unitTestExposedBindingsToIgnore];
+ NSEnumerator *bindingsEnum = [bindings objectEnumerator];
+ NSString *bindingKey;
+ while ((bindingKey = [bindingsEnum nextObject])) {
+ if (![bindingsToIgnore containsObject:bindingKey]) {
+ Class theClass = [object valueClassForBinding:bindingKey];
+ if (!theClass) {
+ [errorArray addObject:[NSString stringWithFormat:@"%@ should have valueClassForBinding '%@'",
+ object, bindingKey]];
+ continue;
+ }
+ @try {
+ @try {
+ [object valueForKey:bindingKey];
+ }
+ @catch (NSException *e) {
+ _GTMDevLog(@"%@ is not key value coding compliant for key %@", object, bindingKey);
+ continue;
+ } // COV_NF_LINE - compiler bug
+ NSDictionary *testValues = [object gtm_unitTestExposedBindingsTestValues:bindingKey];
+ NSEnumerator *testEnum = [testValues keyEnumerator];
+ id testValue;
+ while ((testValue = [testEnum nextObject])) {
+ [object setValue:testValue forKey:bindingKey];
+ id value = [object valueForKey:bindingKey];
+ id desiredValue = [testValues objectForKey:testValue];
+ if (![desiredValue gtm_unitTestIsEqualTo:value]) {
+ [errorArray addObject:[NSString stringWithFormat:@"%@ unequal to %@ for binding '%@'",
+ value, desiredValue, bindingKey]];
+ continue;
+ }
+ }
+ }
+ @catch(NSException *e) {
+ [errorArray addObject:[NSString stringWithFormat:@"%@:%@-> Binding %@",
+ [e name], [e reason], bindingKey]];
+ } // COV_NF_LINE - compiler bug
+ }
+ }
+ } else {
+ [errorArray addObject:[NSString stringWithFormat:@"%@ does not have any exposed bindings",
+ object]];
+ }
+ if (errors) {
+ *errors = errorArray;
+ }
+ return [errorArray count] == 0;
+}
+
+// Utility for simplifying unitTestExposedBindingsTestValues implementations
+@interface NSMutableDictionary (GTMUnitTestingAdditions)
+// Sets an object and a key to the same value in a dictionary.
+- (void)gtm_setObjectAndKey:(id)objectAndKey;
+@end
+
+@implementation NSObject (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [NSMutableArray arrayWithObject:NSValueBinding];
+ if ([[self exposedBindings] containsObject:NSFontBinding]) {
+ NSString *fontBindings[] = { NSFontBoldBinding, NSFontFamilyNameBinding,
+ NSFontItalicBinding, NSFontNameBinding, NSFontSizeBinding };
+ for (size_t i = 0; i < sizeof(fontBindings) / sizeof(NSString*); ++i) {
+ [array addObject:fontBindings[i]];
+ }
+ }
+ return array;
+}
+
+- (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding {
+
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
+ id value = [self valueForKey:binding];
+
+ // Always test identity if possible
+ if (value) {
+ [dict gtm_setObjectAndKey:value];
+ }
+
+ // Now some default test values for a variety of bindings to make
+ // sure that we cover all the bases and save other people writing lots of
+ // duplicate test code.
+
+ // If anybody can think of more to add, please go nuts.
+ if ([binding isEqualToString:NSAlignmentBinding]) {
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:NSLeftTextAlignment]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:NSRightTextAlignment]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:NSCenterTextAlignment]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:NSJustifiedTextAlignment]];
+ NSNumber *natural = [NSNumber numberWithInt:NSNaturalTextAlignment];
+ [dict gtm_setObjectAndKey:natural];
+ [dict setObject:natural forKey:[NSNumber numberWithInt:500]];
+ [dict setObject:natural forKey:[NSNumber numberWithInt:-1]];
+ } else if ([binding isEqualToString:NSAlternateImageBinding] ||
+ [binding isEqualToString:NSImageBinding] ||
+ [binding isEqualToString:NSMixedStateImageBinding] ||
+ [binding isEqualToString:NSOffStateImageBinding] ||
+ [binding isEqualToString:NSOnStateImageBinding]) {
+ // This handles all image bindings
+ [dict gtm_setObjectAndKey:[NSImage imageNamed:@"NSApplicationIcon"]];
+ } else if ([binding isEqualToString:NSAnimateBinding] ||
+ [binding isEqualToString:NSDocumentEditedBinding] ||
+ [binding isEqualToString:NSEditableBinding] ||
+ [binding isEqualToString:NSEnabledBinding] ||
+ [binding isEqualToString:NSHiddenBinding] ||
+ [binding isEqualToString:NSVisibleBinding] ||
+ [binding isEqualToString:NSIsIndeterminateBinding] ||
+ // NSTranparentBinding 10.5 only
+ [binding isEqualToString:@"transparent"]) {
+ // This handles all bool value bindings
+ [dict gtm_setObjectAndKey:[NSNumber numberWithBool:YES]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithBool:NO]];
+ } else if ([binding isEqualToString:NSAlternateTitleBinding] ||
+ [binding isEqualToString:NSHeaderTitleBinding] ||
+ [binding isEqualToString:NSLabelBinding] ||
+ [binding isEqualToString:NSTitleBinding] ||
+ [binding isEqualToString:NSToolTipBinding]) {
+ // This handles all string value bindings
+ [dict gtm_setObjectAndKey:@"happy"];
+ [dict gtm_setObjectAndKey:@""];
+
+ // Test some non-ascii roman text
+ char a_not_alpha[] = { 'A', 0xE2, 0x89, 0xA2, 0xCE, 0x91, '.', 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:a_not_alpha]];
+ // Test some korean
+ char hangugo[] = { 0xED, 0x95, 0x9C, 0xEA, 0xB5,
+ 0xAD, 0xEC, 0x96, 0xB4, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:hangugo]];
+ // Test some japanese
+ char nihongo[] = { 0xE6, 0x97, 0xA5, 0xE6, 0x9C,
+ 0xAC, 0xE8, 0xAA, 0x9E, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:nihongo]];
+ // Test some arabic
+ char arabic[] = { 0xd9, 0x83, 0xd8, 0xa7, 0xd9, 0x83, 0xd8, 0xa7, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:arabic]];
+ } else if ([binding isEqualToString:NSRepresentedFilenameBinding]) {
+ // This handles all path bindings
+ [dict gtm_setObjectAndKey:@"/happy"];
+ [dict gtm_setObjectAndKey:@"/"];
+
+ // Test some non-ascii roman text
+ char a_not_alpha[] = { '/', 'A', 0xE2, 0x89, 0xA2, 0xCE, 0x91, '.', 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:a_not_alpha]];
+ // Test some korean
+ char hangugo[] = { '/', 0xED, 0x95, 0x9C, 0xEA, 0xB5,
+ 0xAD, 0xEC, 0x96, 0xB4, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:hangugo]];
+ // Test some japanese
+ char nihongo[] = { '/', 0xE6, 0x97, 0xA5, 0xE6, 0x9C,
+ 0xAC, 0xE8, 0xAA, 0x9E, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:nihongo]];
+ // Test some arabic
+ char arabic[] = { '/', 0xd9, 0x83, 0xd8, 0xa7, 0xd9, 0x83, 0xd8, 0xa7, 0x00 };
+ [dict gtm_setObjectAndKey:[NSString stringWithUTF8String:arabic]];
+ } else if ([binding isEqualToString:NSMaximumRecentsBinding] ||
+ [binding isEqualToString:NSRowHeightBinding]) {
+ // This handles all int value bindings
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:0]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:-1]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:INT16_MAX]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithInt:INT16_MIN]];
+ } else if ([binding isEqualToString:NSMaxValueBinding] ||
+ [binding isEqualToString:NSMaxWidthBinding] ||
+ [binding isEqualToString:NSMinValueBinding] ||
+ [binding isEqualToString:NSMinWidthBinding] ||
+ [binding isEqualToString:NSContentWidthBinding] ||
+ [binding isEqualToString:NSContentHeightBinding] ||
+ [binding isEqualToString:NSWidthBinding] ||
+ [binding isEqualToString:NSAnimationDelayBinding]) {
+ // This handles all float value bindings
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:0]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:FLT_MAX]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:-FLT_MAX]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:FLT_MIN]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:-FLT_MIN]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:FLT_EPSILON]];
+ [dict gtm_setObjectAndKey:[NSNumber numberWithFloat:-FLT_EPSILON]];
+ } else if ([binding isEqualToString:NSTextColorBinding]) {
+ // This handles all color value bindings
+ [dict gtm_setObjectAndKey:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
+ [dict gtm_setObjectAndKey:[NSColor colorWithCalibratedWhite:1.0 alpha:0.0]];
+ [dict gtm_setObjectAndKey:[NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
+ [dict gtm_setObjectAndKey:[NSColor colorWithCalibratedRed:0.5 green:0.5
+ blue:0.5 alpha:0.5]];
+ [dict gtm_setObjectAndKey:[NSColor colorWithDeviceCyan:0.25 magenta:0.25
+ yellow:0.25 black:0.25
+ alpha:0.25]];
+ } else if ([binding isEqualToString:NSFontBinding]) {
+ // This handles all font value bindings
+ [dict gtm_setObjectAndKey:[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]];
+ [dict gtm_setObjectAndKey:[NSFont toolTipsFontOfSize:[NSFont smallSystemFontSize]]];
+ [dict gtm_setObjectAndKey:[NSFont labelFontOfSize:144.0]];
+ } else if ([binding isEqualToString:NSRecentSearchesBinding] ||
+ [binding isEqualToString:NSSortDescriptorsBinding]) {
+ // This handles all array value bindings
+ [dict gtm_setObjectAndKey:[NSArray array]];
+ } else if ([binding isEqualToString:NSTargetBinding]) {
+ [dict gtm_setObjectAndKey:[NSNull null]];
+ } else {
+ _GTMDevLog(@"Skipped Binding: %@ for %@", binding, self); // COV_NF_LINE
+ }
+ return dict;
+}
+
+- (BOOL)gtm_unitTestIsEqualTo:(id)value {
+ return [self isEqualTo:value];
+}
+
+@end
+
+@implementation NSMutableDictionary (GTMUnitTestingAdditions)
+// Sets an object and a key to the same value in a dictionary.
+- (void)gtm_setObjectAndKey:(id)objectAndKey {
+ [self setObject:objectAndKey forKey:objectAndKey];
+}
+@end
+
+#pragma mark -
+#pragma mark All the special AppKit Bindings issues below
+
+@interface NSImage (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSImage (GTMBindingUnitTestingAdditions)
+- (BOOL)gtm_unitTestIsEqualTo:(id)value {
+ // NSImage just does pointer equality in the default isEqualTo implementation
+ // we need something a little more heavy duty that actually compares the
+ // images internally.
+ return [[self TIFFRepresentation] isEqualTo:[value TIFFRepresentation]];
+}
+@end
+
+@interface NSScroller (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSScroller (GTMBindingUnitTestingAdditions)
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // rdar://5849154 - NSScroller exposes binding with no value class for NSValueBinding
+ [array addObject:NSValueBinding];
+ // rdar://5849236 - NSScroller exposes binding for NSFontBinding
+ [array addObject:NSFontBinding];
+ return array;
+}
+@end
+
+@interface NSTextField (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSTextField (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ for (int i = 0; i < 10; i++) {
+ [array addObject:[NSString stringWithFormat:@"displayPatternValue%d", i]];
+ }
+ return array;
+}
+
+- (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding {
+ NSMutableDictionary *dict = [super gtm_unitTestExposedBindingsTestValues:binding];
+ if ([binding isEqualToString:NSAlignmentBinding]) {
+ // rdar://5851487 - If NSAlignmentBinding for a NSTextField is set to -1 and then got it returns 7
+ [dict setObject:[NSNumber numberWithInt:7] forKey:[NSNumber numberWithInt:-1]];
+ }
+ return dict;
+}
+@end
+
+@interface NSSearchField (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSSearchField (GTMBindingUnitTestingAdditions)
+
+- (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding {
+ NSMutableDictionary *dict = [super gtm_unitTestExposedBindingsTestValues:binding];
+ if ([binding isEqualToString:NSAlignmentBinding]) {
+ // rdar://5851491 - Setting NSAlignmentBinding of search field to NSCenterTextAlignment broken
+ [dict setObject:[NSNumber numberWithInt:NSNaturalTextAlignment]
+ forKey:[NSNumber numberWithInt:NSCenterTextAlignment]];
+ }
+ return dict;
+}
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ [array addObject:NSPredicateBinding];
+ return array;
+}
+
+@end
+
+@interface NSWindow (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSWindow (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ [array addObject:NSContentWidthBinding];
+ [array addObject:NSContentHeightBinding];
+ for (int i = 0; i < 10; i++) {
+ [array addObject:[NSString stringWithFormat:@"displayPatternTitle%d", i]];
+ }
+ return array;
+}
+
+@end
+
+@interface NSBox (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSBox (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ for (int i = 0; i < 10; i++) {
+ [array addObject:[NSString stringWithFormat:@"displayPatternTitle%d", i]];
+ }
+ return array;
+}
+
+@end
+
+@interface NSTableView (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSTableView (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // rdar://5849684 - NSTableView should throw exception when attempting to set NSFontBinding
+ [array addObject:NSFontBinding];
+ // Not KVC Compliant
+ [array addObject:NSContentBinding];
+ [array addObject:NSDoubleClickTargetBinding];
+ [array addObject:NSDoubleClickArgumentBinding];
+ [array addObject:NSSelectionIndexesBinding];
+ return array;
+}
+
+@end
+
+@interface NSTextView (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSTextView (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ //rdar://5849335 - NSTextView only partially KVC compliant for key NSAttributedStringBinding
+ [array addObject:NSAttributedStringBinding];
+ // Not KVC Compliant
+ [array addObject:NSDataBinding];
+ [array addObject:NSValueURLBinding];
+ [array addObject:NSValuePathBinding];
+ return array;
+}
+
+@end
+
+@interface NSTabView (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSTabView (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // rdar://5849248 - NSTabView exposes binding with no value class for NSSelectedIdentifierBinding
+ [array addObject:NSSelectedIdentifierBinding];
+ // Not KVC Compliant
+ [array addObject:NSSelectedIndexBinding];
+ [array addObject:NSSelectedLabelBinding];
+ return array;
+}
+
+@end
+
+@interface NSButton (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSButton (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ [array addObject:NSArgumentBinding];
+ return array;
+}
+
+@end
+
+@interface NSProgressIndicator (GTMBindingUnitTestingAdditions)
+@end
+
+@implementation NSProgressIndicator (GTMBindingUnitTestingAdditions)
+
+- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore {
+ NSMutableArray *array = [super gtm_unitTestExposedBindingsToIgnore];
+ // Not KVC Compliant
+ [array addObject:NSAnimateBinding];
+ return array;
+}
+
+@end