diff options
Diffstat (limited to 'UnitTesting')
37 files changed, 8152 insertions, 932 deletions
diff --git a/UnitTesting/GTMNSView+UnitTesting.h b/UnitTesting/GTMAppKit+UnitTesting.h index fcda16b..5db9ebb 100644..100755 --- a/UnitTesting/GTMNSView+UnitTesting.h +++ b/UnitTesting/GTMAppKit+UnitTesting.h @@ -1,14 +1,8 @@ // -// GTMNSView+UnitTesting.h -// -// Code for making unit testing of graphics/UI easier. Generally you -// will only want to look at the macros: -// GTMAssertDrawingEqualToFile -// GTMAssertViewRepEqualToFile -// and the protocol GTMUnitTestViewDrawer. When using these routines -// make sure you are using device colors and not calibrated/generic colors -// or else your test graphics WILL NOT match across devices/graphics cards. -// +// GTMAppKit+UnitTesting.m +// +// Categories for making unit testing of graphics/UI easier. +// // Copyright 2006-2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -24,12 +18,40 @@ // the License. // -#import <Cocoa/Cocoa.h> +#import <AppKit/AppKit.h> #import "GTMNSObject+UnitTesting.h" +// Categories for making unit testing of graphics/UI easier. +// Allows you to take a state/images of instances of AppKit classes. +// See GTMNSObject+UnitTesting.h for details. + +@interface NSApplication (GTMUnitTestingAdditions) +@end + +@interface NSWindow (GTMUnitTestingAdditions) <GTMUnitTestingImaging> +@end + +@interface NSControl (GTMUnitTestingAdditions) +@end + +@interface NSTextField (GTMUnitTestingAdditions) +@end + +@interface NSCell (GTMUnitTestingAdditions) +@end + +@interface NSImage (GTMUnitTestingAdditions) <GTMUnitTestingImaging> +@end + +@interface NSMenu (GTMUnitTestingAdditions) +@end + +@interface NSMenuItem (GTMUnitTestingAdditions) +@end + @protocol GTMUnitTestViewDrawer; -/// Fails when the |a1|'s drawing in an area |a2| does not equal the TIFF file named |a3|. +// Fails when the |a1|'s drawing in an area |a2| does not equal the image file named |a3|. // See the description of the GTMAssertViewRepEqualToFile macro // to understand how |a3| is found and written out. // See the description of the GTMUnitTestView for a better idea @@ -40,7 +62,7 @@ // a1: The object that implements the GTMUnitTestViewDrawer protocol // that is doing the drawing. // a2: The size of the drawing -// a3: The name of the TIFF file to check against. +// a3: The name of the image file to check against. // Do not include the extension // a4: contextInfo to pass to drawer // description: A format string as in the printf() function. @@ -57,33 +79,17 @@ void *a4ContextInfo = (a4); \ NSRect frame = NSMakeRect(0, 0, a2Size.width, a2Size.height); \ GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ - GTMAssertObjectImageEqualToTIFFNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ + GTMAssertObjectImageEqualToImageNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ } while(0) // Category for making unit testing of graphics/UI easier. -/// Allows you to take a state of a view. Supports both image and state. +// Allows you to take a state of a view. Supports both image and state. // See NSObject+UnitTesting.h for details. -@interface NSView (GTMUnitTestingAdditions) <GTMUnitTestingEncoding> - -/// Returns an image containing a representation suitable for use in comparing against a master image. -// -// NB this means that all colors should be device based. -// -// Returns: -// an image of the object -- (NSImage*)unitTestImage; - -/// Encodes the state of an object in a manner suitable for comparing against a master state file -// This enables us to determine whether the object is in a suitable state. -// -// Arguments: -// inCoder - the coder to encode our state into -- (void)unitTestEncodeState:(NSCoder*)inCoder; - -/// Returns whether unitTestEncodeState should recurse into subviews +@interface NSView (GTMUnitTestingAdditions) <GTMUnitTestingImaging> +// Returns whether unitTestEncodeState should recurse into subviews // -// Dan Waylonis discovered that if you have "Full keyboard access" in the +// If you have "Full keyboard access" in the // Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes // and Lists only" that Apple adds a set of subviews to NSTextFields. So in the // case of NSTextFields we don't want to recurse into their subviews. There may @@ -93,11 +99,12 @@ // // Returns: // should unitTestEncodeState pick up subview state. -- (BOOL)shouldEncodeStateRecurseIntoSubviews; +- (BOOL)gtm_shouldEncodeStateForSubviews; @end -/// A view that allows you to delegate out drawing using the formal GTMUnitTestViewDelegate protocol +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol // This is useful when writing up unit tests for visual elements. // Your test will often end up looking like this: // - (void)testFoo { @@ -108,6 +115,7 @@ // it's content using |self|'s unitTestViewDrawRect method and compares it to // the contents of the file Foo.tif to make sure it's valid @interface GTMUnitTestView : NSView { + @private id<GTMUnitTestViewDrawer> drawer_; // delegate for doing drawing (STRONG) void* contextInfo_; // info passed in by user for them to use when drawing } @@ -132,7 +140,6 @@ // // Args: // rect: the area to draw. -- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo; +- (void)gtm_unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo; @end -/// \endcond diff --git a/UnitTesting/GTMAppKit+UnitTesting.m b/UnitTesting/GTMAppKit+UnitTesting.m new file mode 100755 index 0000000..85f03a2 --- /dev/null +++ b/UnitTesting/GTMAppKit+UnitTesting.m @@ -0,0 +1,304 @@ +// +// GTMAppKit+UnitTesting.m +// +// Categories for making unit testing of graphics/UI easier. +// +// 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 "GTMAppKit+UnitTesting.h" +#import "GTMGeometryUtils.h" +#import "GTMMethodCheck.h" + +@implementation NSApplication (GMUnitTestingAdditions) +GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE + +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeInt:[[self mainWindow] windowNumber] + forKey:@"ApplicationMainWindow"]; + + // Descend down into the windows allowing them to store their state + NSEnumerator *windowEnum = [[self windows] objectEnumerator]; + NSWindow *window = nil; + int i = 0; + while ((window = [windowEnum nextObject])) { + [inCoder encodeObject:window forKey:[NSString stringWithFormat:@"Window %d", i]]; + i = i + 1; + } + + // and encode the menu bar + NSMenu *mainMenu = [self mainMenu]; + if (mainMenu) { + [inCoder encodeObject:mainMenu forKey:@"MenuBar"]; + } +} +@end + +@implementation NSWindow (GMUnitTestingAdditions) + +- (CGImageRef)gtm_createUnitTestImage { + return [[[self contentView] superview] gtm_createUnitTestImage]; +} + +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeObject:[self title] forKey:@"WindowTitle"]; + [inCoder encodeBool:[self isVisible] forKey:@"WindowIsVisible"]; + // Do not record if window is key, because users running unit tests + // and clicking around to other apps, could change this mid test causing + // issues. + // [inCoder encodeBool:[self isKeyWindow] forKey:@"WindowIsKey"]; + [inCoder encodeBool:[self isMainWindow] forKey:@"WindowIsMain"]; + [inCoder encodeObject:[self contentView] forKey:@"WindowContent"]; +} + +@end + +@implementation NSControl (GTMUnitTestingAdditions) + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeObject:[self class] forKey:@"ControlType"]; + [inCoder encodeObject:[self objectValue] forKey:@"ControlValue"]; + [inCoder encodeObject:[self selectedCell] forKey:@"ControlSelectedCell"]; + [inCoder encodeInt:[self tag] forKey:@"ControlTag"]; + [inCoder encodeBool:[self isEnabled] forKey:@"ControlIsEnabled"]; +} + +@end + +@implementation NSTextField (GTMUnitTestingAdditions) + +- (BOOL)gtm_shouldEncodeStateForSubviews { + return NO; +} + +@end + +@implementation NSCell (GTMUnitTestingAdditions) + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + BOOL isImageCell = NO; + if ([self hasValidObjectValue]) { + id val = [self objectValue]; + [inCoder encodeObject:val forKey:@"CellValue"]; + isImageCell = [val isKindOfClass:[NSImage class]]; + } + if (!isImageCell) { + // Image cells have a title that includes addresses that aren't going + // to be constant, so we don't encode them. All the info we need + // is going to be in the CellValue encoding. + [inCoder encodeObject:[self title] forKey:@"CellTitle"]; + } + [inCoder encodeInt:[self state] forKey:@"CellState"]; + [inCoder encodeInt:[self tag] forKey:@"CellTag"]; +} + +@end + +@implementation NSImage (GTMUnitTestingAdditions) + +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeObject:NSStringFromSize([self size]) forKey:@"ImageSize"]; + [inCoder encodeObject:[self name] forKey:@"ImageName"]; +} + +- (CGImageRef)gtm_createUnitTestImage { + // Create up a context + NSSize size = [self size]; + NSRect rect = GTMNSRectOfSize(size); + CGContextRef contextRef = [self gtm_createUnitTestBitmapContextOfSize:GTMNSSizeToCGSize(size) + data:NULL]; + NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithGraphicsPort:contextRef + flipped:NO]; + _GTMDevAssert(bitmapContext, @"Couldn't create ns bitmap context"); + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:bitmapContext]; + [self drawInRect:rect fromRect:rect operation:NSCompositeCopy fraction:1.0]; + + CGImageRef image = CGBitmapContextCreateImage(contextRef); + CFRelease(contextRef); + [NSGraphicsContext restoreGraphicsState]; + return image; +} + +@end + +@implementation NSMenu (GTMUnitTestingAdditions) + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeObject:[self title] forKey:@"MenuTitle"]; + + // Descend down into the menuitems allowing them to store their state + NSEnumerator *menuItemEnum = [[self itemArray] objectEnumerator]; + NSMenuItem *menuItem = nil; + for (int i = 0; (menuItem = [menuItemEnum nextObject]); ++i) { + [inCoder encodeObject:menuItem forKey:[NSString stringWithFormat:@"MenuItem %d", i]]; + } +} + +@end + +@implementation NSMenuItem (GTMUnitTestingAdditions) + +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeObject:[self title] forKey:@"MenuItemTitle"]; + [inCoder encodeObject:[self keyEquivalent] forKey:@"MenuItemKeyEquivalent"]; + [inCoder encodeBool:[self isSeparatorItem] forKey:@"MenuItemIsSeparator"]; + [inCoder encodeInt:[self state] forKey:@"MenuItemState"]; + [inCoder encodeBool:[self isEnabled] forKey:@"MenuItemIsEnabled"]; + [inCoder encodeBool:[self isAlternate] forKey:@"MenuItemIsAlternate"]; + [inCoder encodeObject:[self toolTip] forKey:@"MenuItemTooltip"]; + [inCoder encodeInt:[self tag] forKey:@"MenuItemTag"]; + [inCoder encodeInt:[self indentationLevel] forKey:@"MenuItemIndentationLevel"]; + + // Do our submenu if neccessary + if ([self hasSubmenu]) { + [inCoder encodeObject:[self submenu] forKey:@"MenuItemSubmenu"]; + } +} + +@end + +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol above. This is useful when writing up unit +// tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@implementation GTMUnitTestView + +- (id)initWithFrame:(NSRect)frame + drawer:(id<GTMUnitTestViewDrawer>)drawer + contextInfo:(void*)contextInfo { + self = [super initWithFrame:frame]; + if (self != nil) { + drawer_ = [drawer retain]; + contextInfo_ = contextInfo; + } + return self; +} + +- (void) dealloc { + [drawer_ release]; + [super dealloc]; +} + + +- (void)drawRect:(NSRect)rect { + [drawer_ gtm_unitTestViewDrawRect:rect contextInfo:contextInfo_]; +} + + +@end + +@implementation NSView (GTMUnitTestingAdditions) + +// Returns an image containing a representation of the object +// suitable for use in comparing against a master image. +// Does all of it's drawing with smoothfonts and antialiasing off +// to avoid issues with font smoothing settings and antialias differences +// between ppc and x86. +// +// Returns: +// an image of the object +- (CGImageRef)gtm_createUnitTestImage { + // Create up a context + NSRect bounds = [self bounds]; + CGContextRef contextRef = [self gtm_createUnitTestBitmapContextOfSize:GTMNSSizeToCGSize(bounds.size) + data:NULL]; + NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithGraphicsPort:contextRef + flipped:NO]; + _GTMDevAssert(bitmapContext, @"Couldn't create ns bitmap context"); + + // Save our state and turn off font smoothing and antialias. + CGContextSaveGState(contextRef); + CGContextSetShouldSmoothFonts(contextRef, false); + CGContextSetShouldAntialias(contextRef, false); + [self displayRectIgnoringOpacity:bounds inContext:bitmapContext]; + + CGImageRef image = CGBitmapContextCreateImage(contextRef); + CFRelease(contextRef); + return image; +} + +// Returns whether gtm_unitTestEncodeState should recurse into subviews +// of a particular view. +// If you have "Full keyboard access" in the +// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes +// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the +// case of NSTextFields we don't want to recurse into their subviews. There may +// be other cases like this, so instead of specializing gtm_unitTestEncodeState: to +// look for NSTextFields, NSTextFields will just not allow us to recurse into +// their subviews. +// +// Returns: +// should gtm_unitTestEncodeState pick up subview state. +- (BOOL)gtm_shouldEncodeStateForSubviews { + return YES; +} + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"]; + if ([self gtm_shouldEncodeStateForSubviews]) { + NSEnumerator *subviewEnum = [[self subviews] objectEnumerator]; + NSView *subview = nil; + int i = 0; + while ((subview = [subviewEnum nextObject])) { + [inCoder encodeObject:subview forKey:[NSString stringWithFormat:@"ViewSubView %d", i]]; + i = i + 1; + } + } +} + +@end + diff --git a/UnitTesting/GTMCALayer+UnitTesting.h b/UnitTesting/GTMCALayer+UnitTesting.h new file mode 100644 index 0000000..b757ba9 --- /dev/null +++ b/UnitTesting/GTMCALayer+UnitTesting.h @@ -0,0 +1,46 @@ +// +// GTMCALayer+UnitTesting.h +// +// Code for making unit testing of graphics/UI easier. Generally you +// will only want to look at the macros: +// GTMAssertDrawingEqualToFile +// GTMAssertViewRepEqualToFile +// and the protocol GTMUnitTestCALayerDrawer. When using these routines +// make sure you are using device colors and not calibrated/generic colors +// or else your test graphics WILL NOT match across devices/graphics cards. +// +// 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 <QuartzCore/QuartzCore.h> +#import "GTMNSObject+UnitTesting.h" + +// Category for making unit testing of graphics/UI easier. + +// Allows you to take a state of a view. Supports both image and state. +// See GTMNSObject+UnitTesting.h for details. +@interface CALayer (GTMUnitTestingAdditions) <GTMUnitTestingImaging> +// Returns whether gtm_unitTestEncodeState should recurse into sublayers +// +// Returns: +// should gtm_unitTestEncodeState pick up sublayer state. +- (BOOL)gtm_shouldEncodeStateForSublayers; +@end + +@interface NSObject (GTMCALayerUnitTestingDelegateMethods) +// Delegate method that allows a delegate for a layer to +// decide whether we should recurse +- (BOOL)gtm_shouldEncodeStateForSublayersOfLayer:(CALayer*)layer; +@end diff --git a/UnitTesting/GTMCALayer+UnitTesting.m b/UnitTesting/GTMCALayer+UnitTesting.m new file mode 100644 index 0000000..355f956 --- /dev/null +++ b/UnitTesting/GTMCALayer+UnitTesting.m @@ -0,0 +1,89 @@ +// +// GTMCALayer+UnitTesting.m +// +// Category for making unit testing of graphics/UI easier. +// Allows you to save a view out to a image file, and compare a view +// with a previously stored representation to make sure it hasn't changed. +// +// 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 "GTMCALayer+UnitTesting.h" + +@implementation CALayer (GTMUnitTestingAdditions) + +// Returns an image containing a representation of the object +// suitable for use in comparing against a master image. +// NB this means that all colors should be from "NSDevice" color space +// Does all of it's drawing with smoothfonts and antialiasing off +// to avoid issues with font smoothing settings and antialias differences +// between ppc and x86. +// +// Returns: +// an image of the object +- (CGImageRef)gtm_createUnitTestImage { + CGRect bounds = [self bounds]; + CGSize size = CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds)); + CGContextRef context = [self gtm_createUnitTestBitmapContextOfSize:size + data:NULL]; + _GTMDevAssert(context, @"Couldn't create context"); + + // iPhone renders are flipped + CGAffineTransform transform = CGAffineTransformMakeTranslation(0, size.height); + transform = CGAffineTransformScale(transform, 1.0, -1.0); + CGContextConcatCTM(context, transform); + + [self renderInContext:context]; + CGImageRef image = CGBitmapContextCreateImage(context); + CFRelease(context); + return image; +} + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeBool:[self isHidden] forKey:@"LayerIsHidden"]; + [inCoder encodeBool:[self isDoubleSided] forKey:@"LayerIsDoublesided"]; + [inCoder encodeBool:[self isOpaque] forKey:@"LayerIsOpaque"]; + [inCoder encodeFloat:[self opacity] forKey:@"LayerOpacity"]; + // TODO: There is a ton more we can add here. What are we interested in? + if ([self gtm_shouldEncodeStateForSublayers]) { + int i = 0; + for (CALayer *subLayer in [self sublayers]) { + [inCoder encodeObject:subLayer + forKey:[NSString stringWithFormat:@"CALayerSubLayer %d", i]]; + i = i + 1; + } + } +} + +// Returns whether gtm_unitTestEncodeState should recurse into sublayers +// +// Returns: +// should gtm_unitTestEncodeState pick up sublayer state. +- (BOOL)gtm_shouldEncodeStateForSublayers { + BOOL value = YES; + if([self.delegate respondsToSelector:@selector(gtm_shouldEncodeStateForSublayersOfLayer:)]) { + value = [self.delegate gtm_shouldEncodeStateForSublayersOfLayer:self]; + } + return value; +} + +@end diff --git a/UnitTesting/GTMIPhoneUnitTestMain.m b/UnitTesting/GTMIPhoneUnitTestMain.m new file mode 100755 index 0000000..2af060b --- /dev/null +++ b/UnitTesting/GTMIPhoneUnitTestMain.m @@ -0,0 +1,181 @@ +// +// GTMIPhoneUnitTestMain.m +// +// Copyright 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" +#if !GTM_IPHONE_SDK + #error GTMIPhoneUnitTestMain for iPhone only +#endif +#import <objc/runtime.h> +#import <stdio.h> +#import <UIKit/UIKit.h> +#import "GTMSenTestCase.h" + +// Used for sorting methods below +static int MethodSort(const void *a, const void *b) { + const char *nameA = sel_getName(method_getName(*(Method*)a)); + const char *nameB = sel_getName(method_getName(*(Method*)b)); + return strcmp(nameA, nameB); +} + +@interface UIApplication (iPhoneUnitTestAdditions) +// "Private" method that we need +- (void)terminate; +@end + +@interface GTMIPhoneUnitTestDelegate : NSObject +@end + +@implementation GTMIPhoneUnitTestDelegate + +// Return YES if class is subclass (1 or more generations) of SenTestCase +- (BOOL)isTestFixture:(Class)class { + BOOL iscase = NO; + Class testCaseClass = [SenTestCase class]; + Class superclass; + for (superclass = class; + !iscase && superclass; + superclass = class_getSuperclass(superclass)) { + iscase = superclass == testCaseClass ? YES : NO; + } + return iscase; +} + +// Log an error out to console in a way that Xcode will recognize it. +- (void)printError:(NSString *)error { + if ([error rangeOfString:@"error:"].location == NSNotFound) { + fprintf(stderr, "error: %s\n", [error UTF8String]); + } else { + fprintf(stderr, "%s\n", [error UTF8String]); + } + fflush(stderr); +} + +// Run through all the registered classes and run test methods on any +// that are subclasses of SenTestCase. +- (void)applicationDidFinishLaunching:(UIApplication *)application { + int count = objc_getClassList(NULL, 0); + Class *classes = (Class*)malloc(sizeof(Class) * count); + _GTMDevAssert(classes, @"Couldn't allocate class list"); + objc_getClassList(classes, count); + int suiteSuccesses = 0; + int suiteFailures = 0; + int suiteTotal = 0; + NSString *suiteName = [[NSBundle mainBundle] bundlePath]; + NSDate *suiteStartDate = [NSDate date]; + NSString *suiteStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", + suiteName, suiteStartDate]; + fprintf(stderr, [suiteStartString UTF8String]); + fflush(stderr); + int i, j; + for (i = 0; i < count; ++i) { + Class currClass = classes[i]; + if ([self isTestFixture:currClass]) { + NSDate *fixtureStartDate = [NSDate date]; + NSString *fixtureName = NSStringFromClass(currClass); + NSString *fixtureStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", + fixtureName, fixtureStartDate]; + int fixtureSuccesses = 0; + int fixtureFailures = 0; + int fixtureTotal = 0; + fprintf(stderr, [fixtureStartString UTF8String]); + fflush(stderr); + id testcase = [[currClass alloc] init]; + _GTMDevAssert(testcase, @"Unable to instantiate Test Suite: '%@'\n", + fixtureName); + unsigned int methodCount; + Method *methods = class_copyMethodList(currClass, &methodCount); + // Sort our methods so they are called in Alphabetical order just + // because we can. + qsort(methods, methodCount, sizeof(Method), MethodSort); + for (j = 0; j < methodCount; ++j) { + Method currMethod = methods[j]; + SEL sel = method_getName(currMethod); + const char *name = sel_getName(sel); + // If it starts with test, run it. + if (strstr(name, "test") == name) { + fixtureTotal += 1; + NSDate *caseStartDate = [NSDate date]; + BOOL failed = NO; + @try { + // Wrap things in autorelease pools because they may + // have an STMacro in their dealloc which may get called + // when the pool is cleaned up + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @try { + [testcase setUp]; + [testcase performSelector:sel]; + [testcase tearDown]; + fixtureSuccesses += 1; + } @catch (NSException *exception) { + fixtureFailures += 1; + failed = YES; + [self printError:[exception reason]]; + } + [pool release]; + } @catch (NSException *exception) { + fixtureFailures += 1; + failed = YES; + [self printError:[exception reason]]; + } + NSTimeInterval caseEndTime = [[NSDate date] timeIntervalSinceDate:caseStartDate]; + NSString *caseEndString = [NSString stringWithFormat:@"Test Case '-[%@ %s]' %s (%0.3f seconds).\n", + fixtureName, name, + failed ? "failed" : "passed", caseEndTime]; + fprintf(stderr, [caseEndString UTF8String]); + fflush(stderr); + } + } + if (methods) { + free(methods); + } + [testcase release]; + NSDate *fixtureEndDate = [NSDate date]; + NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate]; + NSString *fixtureEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" + "Executed %d tests, with %d failures (%d unexpected) in %0.3f seconds\n", + fixtureName, fixtureEndDate, fixtureTotal, + fixtureFailures, fixtureFailures, fixtureEndTime]; + + fprintf(stderr, [fixtureEndString UTF8String]); + fflush(stderr); + suiteTotal += fixtureTotal; + suiteSuccesses += fixtureSuccesses; + suiteFailures += fixtureFailures; + } + } + NSDate *suiteEndDate = [NSDate date]; + NSTimeInterval suiteEndTime = [suiteEndDate timeIntervalSinceDate:suiteStartDate]; + NSString *suiteEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" + "Executed %d tests, with %d failures (%d unexpected) in %0.3f seconds\n", + suiteName, suiteEndDate, suiteTotal, + suiteFailures, suiteFailures, suiteEndTime]; + fprintf(stderr, [suiteEndString UTF8String]); + fflush(stderr); + + // Using private call to end our tests + [[UIApplication sharedApplication] terminate]; +} + +@end + +int main(int argc, char *argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, @"GTMIPhoneUnitTestDelegate"); + [pool release]; + return retVal; +} diff --git a/UnitTesting/GTMNSObject+BindingUnitTesting.h b/UnitTesting/GTMNSObject+BindingUnitTesting.h new file mode 100644 index 0000000..947221a --- /dev/null +++ b/UnitTesting/GTMNSObject+BindingUnitTesting.h @@ -0,0 +1,103 @@ +// +// GTMNSObject+BindingUnitTesting.h +// +// Utilities for doing advanced unittesting with object bindings. +// +// 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. +// + +#include <Foundation/Foundation.h> + +// Utility functions for GTMTestExposedBindings Macro. Don't use it directly +// but use the macro below instead +BOOL GTMDoExposedBindingsFunctionCorrectly(NSObject *object, + NSArray **errors); + +// Tests the setters and getters for exposed bindings +// For objects that expose bindings, this tests them for you, saving you from +// having to write a whole pile of set/get test code if you add binding support. +// You will need to implement valueClassForBinding: for your bindings, +// and you may possibly want to implement unitTestExposedBindingsToIgnore +// and unitTestExposedBindingsTestValues. See descriptions of those +// methods below for details. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMTestExposedBindings(a1, description, ...) \ +do { \ + NSObject *a1Object = (a1); \ + NSArray *errors = nil; \ + BOOL isGood = GTMDoExposedBindingsFunctionCorrectly(a1Object, &errors); \ + if (!isGood) { \ + NSEnumerator *errorEnum = [errors objectEnumerator]; \ + NSString *failString; \ + while ((failString = [errorEnum nextObject])) { \ + if (description) { \ + STFail(@"%@: %@", failString, STComposeString(description, ##__VA_ARGS__)); \ + } else { \ + STFail(@"%@", failString); \ + } \ + } \ + } \ +} while(0) + +@interface NSObject (GTMBindingUnitTestingAdditions) +// Allows you to ignore certain bindings when running GTMTestExposedBindings +// If you have bindings you want to ignore, add them to the array returned +// by this method. The standard way to implement this would be: +// - (NSMutableArray*)unitTestExposedBindingsToIgnore { +// NSMutableArray *array = [super unitTestExposedBindingsToIgnore]; +// [array addObject:@"bindingToIgnore1"]; +// ... +// return array; +// } +// The NSObject implementation by default will ignore NSFontBoldBinding, +// NSFontFamilyNameBinding, NSFontItalicBinding, NSFontNameBinding and +// NSFontSizeBinding if your exposed bindings contains NSFontBinding because +// the NSFont*Bindings are NOT KVC/KVO compliant. +- (NSMutableArray*)gtm_unitTestExposedBindingsToIgnore; + +// Allows you to set up test values for your different bindings. +// if you have certain values you want to test against your bindings, add +// them to the dictionary returned by this method. The dictionary is a "value" key +// and an "expected return" object. +// The standard way to implement this would be: +// - (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding { +// NSMutableDictionary *dict = [super unitTestExposedBindingsTestValues:binding]; +// if ([binding isEqualToString:@"myBinding"]) { +// [dict setObject:[[[MySpecialBindingValueSet alloc] init] autorelease] +// forKey:[[[MySpecialBindingValueGet alloc] init] autorelease]]; +// ... +// else if ([binding isEqualToString:@"myBinding2"]) { +// ... +// } +// return array; +// } +// The NSObject implementation handles many of the default bindings, and +// gives you a reasonable set of test values to start. +// See the implementation for the current list of bindings, and values that we +// set for those bindings. +- (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding; + +// A special version of isEqualTo to test whether two binding values are equal +// by default it calls directly to isEqualTo: but can be overridden for special +// cases (like NSImages) where the standard isEqualTo: isn't sufficient. +- (BOOL)gtm_unitTestIsEqualTo:(id)value; +@end 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 diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h index 73a2c9a..c06e001 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.h +++ b/UnitTesting/GTMNSObject+UnitTesting.h @@ -18,91 +18,80 @@ // the License. // -#include <Cocoa/Cocoa.h> +#import "GTMDefines.h" +#import <Foundation/Foundation.h> -/// Fails when image of |a1| does not equal image in TIFF file named |a2| +#if GTM_MACOS_SDK +#import <ApplicationServices/ApplicationServices.h> +#endif + +#import "GTMSenTestCase.h" + +// Utility functions for GTMAssert* Macros. Don't use them directly +// but use the macros below instead +BOOL GTMIsObjectImageEqualToImageNamed(id object, + NSString *filename, + NSString **error); +BOOL GTMIsObjectStateEqualToStateNamed(id object, + NSString *filename, + NSString **error); + +// Fails when image of |a1| does not equal image in image file named |a2| // // Generates a failure when the unittest image of |a1| is not equal to the -// image stored in the TIFF file named |a2|, or |a2| does not exist in the +// image stored in the image file named |a2|, or |a2| does not exist in the // executable code's bundle. -// If |a2| does not exist in the executable code's bundle, we save a TIFF -// representation of |a1| on the desktop with name |a2|. This can then be -// included in the bundle as the master to test against. -// If |a2| != |a1|, we save a TIFF representation of |a1| on the desktop -// with name |a2|_Failed so that we can compare the two files to see what -// has changed. -// See pathForTIFFNamed to see how name is searched for. +// If |a2| does not exist in the executable code's bundle, we save a image +// representation of |a1| in the save directory with name |a2|. This can then +// be included in the bundle as the master to test against. +// If |a2| != |a1|, we save a image representation of |a1| in the save +// directory named |a2|_Failed and a file named |a2|_Failed_Diff showing the +// diff in red so that we can see what has changed. +// See pathForImageNamed to see how name is searched for. +// The save directory is specified by +gtm_setUnitTestSaveToDirectory, and is +// the desktop by default. // Implemented as a macro to match the rest of the SenTest macros. // // Args: -// a1: The object to be checked. Must implement the -unitTestImage method. -// a2: The name of the TIFF file to check against. +// a1: The object to be checked. Must implement the -createUnitTestImage method. +// a2: The name of the image file to check against. // Do not include the extension // description: A format string as in the printf() function. // Can be nil or an empty string but must be present. // ...: A variable number of arguments to the format string. Can be absent. // -#define GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ...) \ +#define GTMAssertObjectImageEqualToImageNamed(a1, a2, description, ...) \ do { \ - NSObject* a1Object = (a1); \ + id a1Object = (a1); \ NSString* a2String = (a2); \ NSString *failString = nil; \ - BOOL isGood = [a1Object respondsToSelector:@selector(unitTestImage)]; \ - if (isGood) { \ - if (![a1Object areSystemSettingsValidForDoingImage]) { \ - break; \ - } \ - NSString *aPath = [a1Object pathForTIFFNamed:a2String]; \ - isGood = aPath != nil; \ - if (isGood) { \ - isGood = [a1Object compareWithTIFFAt:aPath]; \ - } \ - if (!isGood) { \ - if (aPath != nil) { \ - a2String = [a2String stringByAppendingString:@"_Failed"]; \ - } \ - BOOL aSaved = [a1Object saveToTIFFNamed:a2String]; \ - if (NO == aSaved) {\ - if (aPath == nil) { \ - failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ - } else { \ - failString = [NSString stringWithFormat:@"Object image different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ - } \ - } else { \ - if (aPath == nil) { \ - failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ - } else { \ - failString = [NSString stringWithFormat:@"Object image different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ - } \ - } \ - } \ - } else { \ - failString = @"Object does not respond to -unitTestImage"; \ - } \ + BOOL isGood = GTMIsObjectImageEqualToImageNamed(a1Object, a2String, &failString); \ if (!isGood) { \ - if (nil != description) { \ - STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + if (description) { \ + STFail(@"%@: %@", failString, STComposeString(description, ##__VA_ARGS__)); \ } else { \ STFail(@"%@", failString); \ } \ } \ } while(0) -/// Fails when state of |a1| does not equal state in file |a2| +// Fails when state of |a1| does not equal state in file |a2| // // Generates a failure when the unittest state of |a1| is not equal to the // state stored in the state file named |a2|, or |a2| does not exist in the // executable code's bundle. // If |a2| does not exist in the executable code's bundle, we save a state -// representation of |a1| on the desktop with name |a2|. This can then be -// included in the bundle as the master to test against. -// If |a2| != |a1|, we save a state representation of |a1| on the desktop -// with name |a2|_Failed so that we can compare the two files to see what -// has changed. +// representation of |a1| in the save directiry with name |a2|. This can then +// be included in the bundle as the master to test against. +// If |a2| != |a1|, we save a state representation of |a1| in the save +// directory with name |a2|_Failed so that we can compare the two files to see +// what has changed. +// The save directory is specified by +gtm_setUnitTestSaveToDirectory, and is +// the desktop by default. // Implemented as a macro to match the rest of the SenTest macros. // // Args: -// a1: The object to be checked. Must implement the -unitTestImage method. +// a1: The object to be checked. Must implement the -createUnitTestImage method. // a2: The name of the state file to check against. // Do not include the extension // description: A format string as in the printf() function. @@ -111,97 +100,39 @@ do { \ // #define GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ...) \ do { \ - NSObject* a1Object = (a1); \ + id a1Object = (a1); \ NSString* a2String = (a2); \ NSString *failString = nil; \ - BOOL isGood = [a1Object respondsToSelector:@selector(unitTestEncodeState:)]; \ - if (isGood) { \ - NSString *aPath = [a1Object pathForStateNamed:a2String]; \ - isGood = aPath != nil; \ - if (isGood) { \ - isGood = [a1Object compareWithStateAt:aPath]; \ - } \ - if (!isGood) { \ - if (aPath != nil) { \ - a2String = [a2String stringByAppendingString:@"_Failed"]; \ - } \ - BOOL aSaved = [a1Object saveToStateNamed:a2String]; \ - if (NO == aSaved) {\ - if (aPath == nil) { \ - failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ - } else { \ - failString = [NSString stringWithFormat:@"Object state different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ - } \ - } else { \ - if (aPath == nil) { \ - failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ - } else { \ - failString = [NSString stringWithFormat:@"Object state different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ - } \ - } \ - } \ - } else { \ - failString = @"Object does not respond to -unitTestEncodeState:"; \ - } \ + BOOL isGood = GTMIsObjectStateEqualToStateNamed(a1Object, a2String, &failString); \ if (!isGood) { \ - if (nil != description) { \ - STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + if (description) { \ + STFail(@"%@: %@", failString, STComposeString(description, ##__VA_ARGS__)); \ } else { \ STFail(@"%@", failString); \ } \ } \ -} while(0) +} while(0); -/// test both GTMAssertObjectImageEqualToTIFFNamed and GTMAssertObjectStateEqualToStateNamed +// test both GTMAssertObjectImageEqualToImageNamed and GTMAssertObjectStateEqualToStateNamed // // Combines the above two macros into a single ubermacro for comparing // both state and image. When only the best will do... #define GTMAssertObjectEqualToStateAndImageNamed(a1, a2, description, ...) \ do { \ - GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ##__VA_ARGS__); \ + GTMAssertObjectImageEqualToImageNamed(a1, a2, description, ##__VA_ARGS__); \ GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ##__VA_ARGS__); \ } while (0) -/// Tests the setters and getters for exposed bindings -// For objects that expose bindings, this tests them for you, saving you from -// having to write a whole pile of set/get test code if you add binding support. -// You will need to implement valueClassForBinding: for your bindings, -// and you may possibly want to implement unitTestExposedBindingsToIgnore -// and unitTestExposedBindingsTestValues. See descriptions of those -// methods below for details. -// Implemented as a macro to match the rest of the SenTest macros. +// GTMUnitTestingImaging protocol is for objects which need to save their +// image for using with the unit testing categories +@protocol GTMUnitTestingImaging +// Create a CGImageRef containing a representation suitable for use in +// comparing against a master image. // -// Args: -// a1: The object to be checked. -// description: A format string as in the printf() function. -// Can be nil or an empty string but must be present. -// ...: A variable number of arguments to the format string. Can be absent. -// -#define GTMTestExposedBindings(a1, description, ...) \ -do { \ - NSArray *bindings = [a1 exposedBindings]; \ - if (bindings) { \ - NSArray *bindingsToIgnore = [a1 unitTestExposedBindingsToIgnore]; \ - NSEnumerator *bindingsEnum = [bindings objectEnumerator]; \ - NSString *bindingKey; \ - while ((bindingKey = [bindingsEnum nextObject])) { \ - if (![bindingsToIgnore containsObject:bindingKey]) { \ - Class theClass = [a1 valueClassForBinding:bindingKey]; \ - STAssertNotNil(theClass, @"Should have valueClassForBinding %@", bindingKey); \ - NSDictionary *testValues = [a1 unitTestExposedBindingsTestValues:bindingKey]; \ - NSEnumerator *testEnum = [testValues keyEnumerator]; \ - id testValue; \ - while ((testValue = [testEnum nextObject])) { \ - [a1 setValue:testValue forKey:bindingKey]; \ - id value = [a1 valueForKey:bindingKey]; \ - STAssertEqualObjects([testValues objectForKey:testValue], value, description, ##__VA_ARGS__); \ - } \ - } \ - } \ - } \ -} while(0) - -/// \cond Protocols +// Returns: +// an CGImageRef of the object. Caller must release +- (CGImageRef)gtm_createUnitTestImage; +@end // GTMUnitTestingEncoding protocol is for objects which need to save their // "state" for using with the unit testing categories @@ -213,29 +144,18 @@ do { \ // // Arguments: // inCoder - the coder to encode our state into -- (void)unitTestEncodeState:(NSCoder*)inCoder; +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder; @end -/// Category for saving and comparing object state and image for unit tests +// Category for saving and comparing object state and image for unit tests // // The GTMUnitTestAdditions category gives object the ability to store their // state for use in unittesting in two different manners. -// 1) Objects can elect to save their "image" as a TIFF that we can compare at -// runtime to a TIFF on file to make sure that the representation hasn't +// 1) Objects can elect to save their "image" that we can compare at +// runtime to an image file to make sure that the representation hasn't // changed. All views and Windows can save their image. In the case of Windows, // they are "bluescreened" so that any transparent areas can be compared between -// machines. For this to work, the appearance must be set to "Aqua blue" In the -// case of NSWindows and NSScreens, we do a screen capture operation to capture -// their image. In these cases, font smoothing settings must be set consistently -// across machines. The current standard is -// Font Smoothing Style: Standard - Best For CRT -// Turn Off Text Smoothing For Font Sizes: 8 And Smaller -// If you do not have these settings, any unit tests depending on them will not -// be executed, and a warning will be logged. -// Also, we need to be careful about avoiding ColorSync. In most cases the -// unittesting system handles this for you. If you are running into troubles -// make sure that you are using device colors, and not calibrated colors -// wherever you are doing drawing. +// machines. // 2) Objects can elect to save their "state". State is the attributes that we // want to verify when running unit tests. Applications, Windows, Views, // Controls and Cells currently return a variety of state information. If you @@ -245,131 +165,155 @@ do { \ // information if appropriate via the unitTestEncoderDidEncode:inCoder: delegate // method. // To compare state/image in your unit tests, you can use the three macros above -// GTMAssertObjectStateEqualToStateNamed, GTMAssertObjectImageEqualToTIFFNamed and +// GTMAssertObjectStateEqualToStateNamed, GTMAssertObjectImageEqualToImageNamed and // GTMAssertObjectEqualToStateAndImageNamed. @interface NSObject (GTMUnitTestingAdditions) <GTMUnitTestingEncoding> -/// Returns an image containing a representation suitable for use in comparing against a master image. -// -// NB this means that all colors should be -// device based, as colorsynced colors will be different on different devices. +// Allows you to control where the unit test utilities save any files +// (image or state) that they create on your behalf. By default they +// will save to the desktop. ++ (void)gtm_setUnitTestSaveToDirectory:(NSString*)path; ++ (NSString *)gtm_getUnitTestSaveToDirectory; + +// Create a CGColorSpaceRef appropriate for using in creating a unit test image +// iPhone uses device colorspace. +// Returns: +// an CGColorSpaceRef of the object. Caller must release +- (CGColorSpaceRef)gtm_createUnitTestColorspace; + +// Create a CGBitmapContextRef appropriate for using in creating a unit test +// image. If data is non-NULL, returns the buffer that the bitmap is +// using for it's underlying storage. You must free this buffer using +// free. If data is NULL, uses it's own internal storage. +// In either case, it will be filled with transparency. // // Returns: -// an image of the object -- (NSImage*)unitTestImage; - -/// Checks to see that system settings are valid for doing an image comparison. -// The main issue is that we make sure that we are set to using Blue Aqua as -// our appearance. -// Instead of directly overriding this, a unit test can just use: -// needsAquaBlueAppearanceForDoingImage -// needsScrollBarArrowsLowerRightForDoingImage -// to enable those tests w/in this base implementation. -// The other issues are for NSScreen and NSWindow images as they are affected by -// the font smoothing settings in the system preferences. For things to work -// these settings must be set to: -// Font Smoothing Style: Standard - Best For CRT -// Turn Off Text Smoothing For Font Sizes: 8 And Smaller +// an CGContextRef of the object. Caller must release +- (CGContextRef)gtm_createUnitTestBitmapContextOfSize:(CGSize)size + data:(unsigned char **)data; + +// Checks to see that system settings are valid for doing an image comparison. +// Most of these are set by our unit test app. See the unit test app main.m +// for details. // // Returns: // YES if we can do image comparisons for this object type. -- (BOOL)areSystemSettingsValidForDoingImage; +- (BOOL)gtm_areSystemSettingsValidForDoingImage; + +// Return the type of image to work with. Only valid types on the iPhone +// are kUTTypeJPEG and kUTTypePNG. MacOS supports several more. +- (NSString*)gtm_imageUTI; -/// Checks if this test needs the AquaBlue Appearance for doing the image comparison. -// If the test uses the appearance colors, this should be overriden to return -// YES (ie-default is no). This provides a hook so the unittest can be skipped -// if the running user's settings aren't the "standard" for the UI unitttests. +// Return the extension to be used for saving unittest images // -// Returns: -// YES if this test needs the AquaBlue Appearance. -- (BOOL)needsAquaBlueAppearanceForDoingImage; +// Returns +// An extension (e.g. "png") +- (NSString*)gtm_imageExtension; -/// Checks if this test needs the ScrollBarArrows LowerRight for doing the image comparison. -// If the test uses the scrollbar drawing, this should be overriden to return -// YES (ie-default is no). This provides a hook so the unittest can be skipped -// if the running user's settings aren't the "standard" for the UI unitttests. +// Return image data in the format expected for gtm_imageExtension +// So for a "png" extension I would expect "png" data // -// Returns: -// YES if this test needs the ScrollBarArrows LowerRight. -- (BOOL)needsScrollBarArrowsLowerRightForDoingImage; +// Returns +// NSData for image +- (NSData*)gtm_imageDataForImage:(CGImageRef)image; -/// Save the unitTestImage to a TIFF file with name |name| at ~/Desktop/|name|.tif. -// The TIFF will be compressed with LZW. +// Save the unitTestImage to a image file with name +// |name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension +// in the save folder (desktop by default) // // Args: -// name: The name for the TIFF file you would like saved. +// name: The name for the image file you would like saved. // // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToTIFFNamed:(NSString*)name; +- (BOOL)gtm_saveToImageNamed:(NSString*)name; -/// Save unitTestImage of |self| to a TIFF file at path |path|. -// The TIFF will be compressed with LZW. All non-drawn areas will be transparent. +// Save unitTestImage of |self| to an image file at path |path|. +// All non-drawn areas will be transparent. // // Args: -// name: The name for the TIFF file you would like saved. +// name: The name for the image file you would like saved. // // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToTIFFAt:(NSString*)path; +- (BOOL)gtm_saveToImageAt:(NSString*)path; -/// Compares unitTestImage of |self| to the TIFF located at |path| +// Compares unitTestImage of |self| to the image located at |path| // // Args: -// path: the path to the TIFF file you want to compare against. +// path: the path to the image file you want to compare against. +// If diff is non-nil, it will contain an auto-released diff of the images. // // Returns: // YES if they are equal, NO is they are not +// If diff is non-nil, it will contain a diff of the images. Must +// be released by caller. // -- (BOOL)compareWithTIFFNamed:(NSString*)name; +- (BOOL)gtm_compareWithImageAt:(NSString*)path diffImage:(CGImageRef*)diff; -/// Compares unitTestImage of |self| to the TIFF located at |path| +// Find the path for a image by name in your bundle. +// Searches for the following: +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.arch.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.extension" +// "name.OSVersionMajor.OSVersionMinor.arch.extension" +// "name.arch.OSVersionMajor.extension" +// "name.OSVersionMajor.arch.extension" +// "name.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension" +// "name.OSVersionMajor.OSVersionMinorextension" +// "name.OSVersionMajor.extension" +// "name.extension" +// Do not include the extension on your name. // // Args: -// path: the path to the TIFF file you want to compare against. +// name: The name for the image file you would like to find. // // Returns: -// YES if they are equal, NO is they are not +// the path if the image exists in your bundle +// or nil if no image to be found // -- (BOOL)compareWithTIFFAt:(NSString*)path; +- (NSString *)gtm_pathForImageNamed:(NSString*)name; -/// Find the path for a TIFF by name in your bundle. -// Searches for the following: -// "name.tif", -// "name.arch.tif", -// "name.arch.OSVersionMajor.tif" -// "name.arch.OSVersionMajor.OSVersionMinor.tif" -// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" -// "name.arch.OSVersionMajor.tif" -// "name.OSVersionMajor.arch.tif" -// "name.OSVersionMajor.OSVersionMinor.arch.tif" -// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.tif" -// "name.OSVersionMajor.tif" -// "name.OSVersionMajor.OSVersionMinor.tif" -// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" -// Do not include the ".tif" extension on your name. +// Generates a CGImageRef from the image at |path| +// Args: +// path: The path to the image. +// +// Returns: +// A CGImageRef that you own, or nil if no image at path +- (CGImageRef)gtm_createImageUsingPath:(NSString*)path; + +// Generates a path for a image in the save directory, which is desktop +// by default. +// Path will be: +// SaveDir/|name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension // // Args: -// name: The name for the TIFF file you would like to find. +// name: The name for the image file you would like to generate a path for. // // Returns: -// the path if the TIFF exists in your bundle -// or nil if no TIFF to be found +// the path // -- (NSString *)pathForTIFFNamed:(NSString*)name; - +- (NSString *)gtm_saveToPathForImageNamed:(NSString*)name; -/// Gives us a LZW compressed representation of unitTestImage of |self|. +// Gives us a representation of unitTestImage of |self|. // // Returns: -// a LZW compressed TIFF if successful +// a representation if successful // nil if failed // -- (NSData *)TIFFRepresentation; +- (NSData *)gtm_imageRepresentation; +// Return the extension to be used for saving unittest states +// +// Returns +// An extension (e.g. "gtmUTState") +- (NSString*)gtm_stateExtension; -/// Save the encoded unit test state to a .gtmUTState file with name |name| at ~/Desktop/|name|.gtmUTState. +// Save the encoded unit test state to a state file with name +// |name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension +// in the save folder (desktop by default) // // Args: // name: The name for the state file you would like saved. @@ -377,9 +321,9 @@ do { \ // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToStateNamed:(NSString*)name; +- (BOOL)gtm_saveToStateNamed:(NSString*)name; -/// Save encoded unit test state of |self| to a .gtmUTState file at path |path|. +// Save encoded unit test state of |self| to a state file at path |path|. // // Args: // name: The name for the state file you would like saved. @@ -387,19 +331,9 @@ do { \ // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToStateAt:(NSString*)path; +- (BOOL)gtm_saveToStateAt:(NSString*)path; -/// Compares encoded unit test state of |self| to the .gtmUTState named |name| -// -// Args: -// name: the name of the state file you want to compare against. -// -// Returns: -// YES if they are equal, NO is they are not -// -- (BOOL)compareWithStateNamed:(NSString*)name; - -/// Compares encoded unit test state of |self| to the .gtmUTState located at |path|. +// Compares encoded unit test state of |self| to the state file located at |path| // // Args: // path: the path to the state file you want to compare against. @@ -407,23 +341,23 @@ do { \ // Returns: // YES if they are equal, NO is they are not // -- (BOOL)compareWithStateAt:(NSString*)path; +- (BOOL)gtm_compareWithStateAt:(NSString*)path; + -/// Find the path for a state by name in your bundle. +// Find the path for a state by name in your bundle. // Searches for: -// "name.gtmUTState", -// "name.arch.gtmUTState", -// "name.arch.OSVersionMajor.gtmUTState" -// "name.arch.OSVersionMajor.OSVersionMinor.gtmUTState" -// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" -// "name.arch.OSVersionMajor.gtmUTState" -// "name.OSVersionMajor.arch.gtmUTState" -// "name.OSVersionMajor.OSVersionMinor.arch.gtmUTState" -// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.gtmUTState" -// "name.OSVersionMajor.gtmUTState" -// "name.OSVersionMajor.OSVersionMinor.gtmUTState" -// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" -// Do not include the ".gtmUTState" extension on your name. +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.arch.extension" +// "name.arch.OSVersionMajor.OSVersionMinor.extension" +// "name.OSVersionMajor.OSVersionMinor.arch.extension" +// "name.arch.OSVersionMajor.extension" +// "name.OSVersionMajor.arch.extension" +// "name.arch.extension" +// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension" +// "name.OSVersionMajor.OSVersionMinor.extension" +// "name.OSVersionMajor.extension" +// "name.extension" +// Do not include the extension on your name. // // Args: // name: The name for the state file you would like to find. @@ -432,19 +366,29 @@ do { \ // the path if the state exists in your bundle // or nil if no state to be found // -- (NSString *)pathForStateNamed:(NSString*)name; +- (NSString *)gtm_pathForStateNamed:(NSString*)name; +// Generates a path for a state in the save directory, which is desktop +// by default. +// Path will be: +// SaveDir/|name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension +// +// Args: +// name: The name for the state file you would like to generate a path for. +// +// Returns: +// the path +// +- (NSString *)gtm_saveToPathForStateNamed:(NSString*)name; -/// Gives us the encoded unit test state for |self| +// Gives us the encoded unit test state for |self| // // Returns: // the encoded state if successful // nil if failed // -- (NSDictionary *)stateRepresentation; - +- (NSDictionary *)gtm_stateRepresentation; -/// Encodes the state of an object // Encodes the state of an object in a manner suitable for comparing // against a master state file so we can determine whether the // object is in a suitable state. Encode data in the coder in the same @@ -452,52 +396,7 @@ do { \ // // Arguments: // inCoder - the coder to encode our state into -- (void)unitTestEncodeState:(NSCoder*)inCoder; - -/// Allows you to ignore certain bindings when running GTMTestExposedBindings -// If you have bindings you want to ignore, add them to the array returned -// by this method. The standard way to implement this would be: -// - (NSMutableArray*)unitTestExposedBindingsToIgnore { -// NSMutableArray *array = [super unitTestExposedBindingsToIgnore]; -// [array addObject:@"bindingToIgnore1"]; -// ... -// return array; -// } -// The NSObject implementation by default will ignore NSFontBoldBinding, -// NSFontFamilyNameBinding, NSFontItalicBinding, NSFontNameBinding and -// NSFontSizeBinding if your exposed bindings contains NSFontBinding because -// the NSFont*Bindings are NOT KVC/KVO compliant, and they just happen to work -// through what can only be described as magic :) -- (NSMutableArray*)unitTestExposedBindingsToIgnore; - -/// Allows you to set up test values for your different bindings. -// if you have certain values you want to test against your bindings, add -// them to the dictionary returned by this method. The dictionary is a "value" key -// and an "expected return" object. -// The standard way to implement this would be: -// - (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding { -// NSMutableDictionary *dict = [super unitTestExposedBindingsTestValues:binding]; -// if ([binding isEqualToString:@"myBinding"]) { -// [dict setObject:[[[MySpecialBindingValueSet alloc] init] autorelease] -// forKey:[[[MySpecialBindingValueGet alloc] init] autorelease]]; -// [dict setObjectAndKey:[[[MySpecialBindingValue alloc] init] autorelease]]; -// ... -// else if ([binding isEqualToString:@"myBinding2"]) { -// ... -// } -// return array; -// } -// The NSObject implementation handles many of the default bindings, and -// gives you a reasonable set of test values to start. -// See the implementation for the current list of bindings, and values that we -// set for those bindings. -- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding; -@end - -// Utility for simplifying unitTestExposedBindingsTestValues implementations -@interface NSMutableDictionary (GTMUnitTestingAdditions) -// Sets an object and a key to the same value in a dictionary. -- (void)setObjectAndKey:(id)objectAndKey; +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder; @end // Informal protocol for delegates that wanst to be able to add state info @@ -506,11 +405,9 @@ do { \ // Delegate function for unit test objects that have delegates. Delegates have // the option of encoding more data into the coder to store their state for // unittest usage. -- (void)unitTestEncoderDidEncode:(id)sender inCoder:(NSCoder*)inCoder; +- (void)gtm_unitTestEncoderWillEncode:(id)sender inCoder:(NSCoder*)inCoder; @end -/// \endcond - // Whenever an object is encoded by the unit test encoder, it send out a // notification so that objects who want to add data to the encoded objects unit // test state can do so. The Coder will be in the userInfo dictionary for the @@ -520,15 +417,3 @@ extern NSString *const GTMUnitTestingEncodedObjectNotification; // Key for finding the encoder in the userInfo dictionary for // GTMUnitTestingEncodedObjectNotification notifications. extern NSString *const GTMUnitTestingEncoderKey; - - -/// Support for Pulse automated builds -@interface NSObject (GTMUnitTestingPulseAdditions) - -// Determine if the current unittest is running under Pulse -- (BOOL)isRunningUnderPulse; - -// Get the current base directory for Pulse -- (NSString *)pulseBaseDirectory; - -@end diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m index 5be6b9f..6aff70c 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.m +++ b/UnitTesting/GTMNSObject+UnitTesting.m @@ -18,16 +18,25 @@ // the License. // -#include <Carbon/Carbon.h> -#include <mach-o/arch.h> +#import <mach-o/arch.h> #import "GTMNSObject+UnitTesting.h" -#import "GTMNSWorkspace+Theme.h" #import "GTMSystemVersion.h" +#import "GTMGarbageCollection.h" + +#if GTM_IPHONE_SDK +#import <UIKit/UIKit.h> +#endif NSString *const GTMUnitTestingEncodedObjectNotification = @"GTMUnitTestingEncodedObjectNotification"; NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; +#if GTM_IPHONE_SDK +// No UTIs on iPhone. Only two we need. +const CFStringRef kUTTypePNG = CFSTR("public.png"); +const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg"); +#endif + // This class exists so that we can locate our bundle using [NSBundle // bundleForClass:]. We don't use [NSBundle mainBundle] because when we are // being run as a unit test, we aren't the mainBundle @@ -41,6 +50,129 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; // Nothing here. We're just interested in the name for finding our bundle. @end +BOOL GTMIsObjectImageEqualToImageNamed(id object, + NSString* filename, + NSString **error) { + NSString *failString = nil; + if (error) { + *error = nil; + } + BOOL isGood = [object respondsToSelector:@selector(gtm_createUnitTestImage)]; + if (isGood) { + if ([object gtm_areSystemSettingsValidForDoingImage]) { + NSString *aPath = [object gtm_pathForImageNamed:filename]; + CGImageRef diff = nil; + isGood = aPath != nil; + if (isGood) { + isGood = [object gtm_compareWithImageAt:aPath diffImage:&diff]; + } + if (!isGood) { + if (aPath) { + filename = [filename stringByAppendingString:@"_Failed"]; + } + BOOL aSaved = [object gtm_saveToImageNamed:filename]; + NSString *fileNameWithExtension = [NSString stringWithFormat:@"%@.%@", + filename, [object gtm_imageExtension]]; + NSString *fullSavePath = [object gtm_saveToPathForImageNamed:filename]; + if (NO == aSaved) { + if (!aPath) { + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. " + "Tried to save as %@ and failed.", + fileNameWithExtension, fullSavePath]; + } else { + failString = [NSString stringWithFormat:@"Object image different than file %@. " + "Tried to save as %@ and failed.", + aPath, fullSavePath]; + } + } else { + if (!aPath) { + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. " + "Saved to %@", fileNameWithExtension, fullSavePath]; + } else { + NSString *diffPath = [filename stringByAppendingString:@"_Diff"]; + diffPath = [object gtm_saveToPathForImageNamed:diffPath]; + NSData *data = nil; + if (diff) { + data = [object gtm_imageDataForImage:diff]; + CFRelease(diff); + } + if ([data writeToFile:diffPath atomically:YES]) { + failString = [NSString stringWithFormat:@"Object image different " + "than file %@. Saved image to %@. Saved diff to %@", + aPath, fullSavePath, diffPath]; + } else { + failString = [NSString stringWithFormat:@"Object image different " + "than file %@. Saved image to %@. Unable to save " + "diff. Most likely the image and diff are " + "different sizes.", + aPath, fullSavePath]; + } + } + } + } + } else { + failString = @"systemSettings not valid for taking image"; // COV_NF_LINE + } + } else { + failString = @"Object does not conform to GTMUnitTestingImaging protocol"; + } + if (error) { + *error = failString; + } + return isGood; +} + +BOOL GTMIsObjectStateEqualToStateNamed(id object, + NSString* filename, + NSString **error) { + NSString *failString = nil; + if (error) { + *error = nil; + } + BOOL isGood = [object conformsToProtocol:@protocol(GTMUnitTestingEncoding)]; + if (isGood) { + NSString *aPath = [object gtm_pathForStateNamed:filename]; + isGood = aPath != nil; + if (isGood) { + isGood = [object gtm_compareWithStateAt:aPath]; + } + if (!isGood) { + if (aPath) { + filename = [filename stringByAppendingString:@"_Failed"]; + } + BOOL aSaved = [object gtm_saveToStateNamed:filename]; + NSString *fileNameWithExtension = [NSString stringWithFormat:@"%@.%@", + filename, [object gtm_stateExtension]]; + NSString *fullSavePath = [object gtm_saveToPathForStateNamed:filename]; + if (NO == aSaved) { + if (!aPath) { + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. " + "Tried to save as %@ and failed.", + fileNameWithExtension, fullSavePath]; + } else { + failString = [NSString stringWithFormat:@"Object state different than file %@. " + "Tried to save as %@ and failed.", + aPath, fullSavePath]; + } + } else { + if (!aPath) { + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. " + "Saved to %@", fileNameWithExtension, fullSavePath]; + } else { + failString = [NSString stringWithFormat:@"Object state different than file %@. " + "Saved to %@", aPath, fullSavePath]; + } + } + } + } else { + failString = @"Object does not conform to GTMUnitTestingEncoding protocol"; + } + if (error) { + *error = failString; + } + return isGood; +} + @interface NSObject (GTMUnitTestingAdditionsPrivate) /// Find the path for a file named name.extension in your bundle. // Searches for the following: @@ -66,9 +198,10 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; // the path if the file exists in your bundle // or nil if no file is found // -- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension; -- (NSString *)saveToPathForFileNamed:(NSString*)name - extension:(NSString*)extension; +- (NSString *)gtm_pathForFileNamed:(NSString*)name extension:(NSString*)extension; +- (NSString *)gtm_saveToPathForFileNamed:(NSString*)name + extension:(NSString*)extension; +- (CGImageRef)gtm_createUnitTestImage; @end // This is a keyed coder for storing unit test state data. It is used only by @@ -85,6 +218,13 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; - (NSDictionary*)dictionary; @end +// Small utility function for checking to see if a is b +/- 1. +static inline BOOL almostEqual(unsigned char a, unsigned char b) { + unsigned char diff = a > b ? a - b : b - a; + BOOL notEqual = diff < 2; + return notEqual; +} + @implementation GTMUnitTestingKeyedCoder // Set up storage for coder. Stores type and version. @@ -95,7 +235,7 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; - (id)init { self = [super init]; if (self != nil) { - dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0]; + dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:2]; [dictionary_ setObject:@"GTMUnitTestingArchive" forKey:@"$GTMArchive"]; // Version number can be changed here. @@ -117,14 +257,14 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; // Arguments: // key - key to check for in dictionary - (void)checkForKey:(NSString*)key { - NSAssert1(![dictionary_ objectForKey:key], @"Key already exists for %@", key); + _GTMDevAssert(![dictionary_ objectForKey:key], @"Key already exists for %@", key); } // Key routine for the encoder. We store objects in our dictionary based on // their key. As we encode objects we send out notifications to let other // classes doing tests add their specific data to the base types. If we can't -// encode the object (it doesn't support unitTestEncodeState) and we don't get -// any info back from the notifier, we attempt to store it's description. +// encode the object (it doesn't support gtm_unitTestEncodeState) and we don't +// get any info back from the notifier, we attempt to store it's description. // // Arguments: // objv - object to be encoded @@ -139,10 +279,10 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; NSMutableDictionary *curDictionary = dictionary_; dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0]; - // If objv responds to unitTestEncodeState get it to record + // If objv responds to gtm_unitTestEncodeState get it to record // its data. - if ([objv respondsToSelector:@selector(unitTestEncodeState:)]) { - [objv unitTestEncodeState:self]; + if ([objv respondsToSelector:@selector(gtm_unitTestEncodeState:)]) { + [objv gtm_unitTestEncodeState:self]; } // We then send out a notification to let other folks @@ -164,7 +304,7 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; if (description && [description rangeOfString:@"0x"].length == 0) { [curDictionary setObject:description forKey:key]; } else { - NSAssert1(NO, @"Unable to encode forKey: %@", key); + _GTMDevAssert(NO, @"Unable to encode forKey: %@", key); // COV_NF_LINE } } [dictionary_ release]; @@ -176,10 +316,6 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; // Arguments: // *v - value to encode // key - key to encode it in -- (void)encodeConditionalObject:(id)objv forKey:(NSString *)key { - [self checkForKey:key]; - [self encodeObject:(id)objv forKey:key]; -} - (void)encodeBool:(BOOL)boolv forKey:(NSString *)key { [self checkForKey:key]; @@ -211,9 +347,13 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; [dictionary_ setObject:[NSNumber numberWithDouble:realv] forKey:key]; } -- (void)encodeBytes:(const uint8_t *)bytesp length:(unsigned)lenv forKey:(NSString *)key { +- (void)encodeBytes:(const uint8_t *)bytesp + length:(unsigned)lenv + forKey:(NSString *)key { [self checkForKey:key]; - [dictionary_ setObject:[NSData dataWithBytesNoCopy:(uint8_t*)bytesp length:lenv] forKey:key]; + [dictionary_ setObject:[NSData dataWithBytes:(uint8_t*)bytesp + length:lenv] + forKey:key]; } // Get our storage back as an NSDictionary @@ -226,11 +366,41 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; @end +static NSString *gGTMUnitTestSaveToDirectory = nil; @implementation NSObject (GTMUnitTestingAdditions) -// GTM_METHOD_CHECK(NSWorkspace, themeAppearance); -// GTM_METHOD_CHECK(NSWorkspace, themeScrollBarArrowStyle); ++ (void)gtm_setUnitTestSaveToDirectory:(NSString*)path { + @synchronized([self class]) { + [gGTMUnitTestSaveToDirectory autorelease]; + gGTMUnitTestSaveToDirectory = [path copy]; + } +} + ++ (NSString *)gtm_getUnitTestSaveToDirectory { + NSString *result = nil; + @synchronized([self class]) { + if (!gGTMUnitTestSaveToDirectory) { +#if GTM_IPHONE_SDK + // Developer build, use their home directory Desktop. + gGTMUnitTestSaveToDirectory = [[[[[NSHomeDirectory() stringByDeletingLastPathComponent] + stringByDeletingLastPathComponent] + stringByDeletingLastPathComponent] + stringByDeletingLastPathComponent] + stringByAppendingPathComponent:@"Desktop"]; +#else + NSArray *desktopDirs = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, + NSUserDomainMask, + YES); + gGTMUnitTestSaveToDirectory = [desktopDirs objectAtIndex:0]; +#endif + [gGTMUnitTestSaveToDirectory retain]; + } + result = gGTMUnitTestSaveToDirectory; + } + + return result; +} /// Find the path for a file named name.extension in your bundle. // Searches for the following: @@ -256,36 +426,48 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; // the path if the file exists in your bundle // or nil if no file is found // -- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension { +- (NSString *)gtm_pathForFileNamed:(NSString*)name + extension:(NSString*)extension { NSString *thePath = nil; Class bundleClass = [GTMUnitTestingAdditionsBundleFinder class]; NSBundle *myBundle = [NSBundle bundleForClass:bundleClass]; - NSAssert3(myBundle, @"Couldn't find bundle for class: %@ searching for file:%@.%@", + _GTMDevAssert(myBundle, @"Couldn't find bundle for class: %@ searching for file:%@.%@", NSStringFromClass(bundleClass), name, extension); - - // Extensions - NSString *extensions[2]; - const NXArchInfo *localInfo = NXGetLocalArchInfo(); - NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); - extensions[0] = [NSString stringWithUTF8String:localInfo->name]; - extensions[1] = @""; - // System Version long major, minor, bugFix; [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; NSString *systemVersions[4]; - systemVersions[0] = [NSString stringWithFormat:@".%d.%d.%d", major, minor, bugFix]; + systemVersions[0] = [NSString stringWithFormat:@".%d.%d.%d", + major, minor, bugFix]; systemVersions[1] = [NSString stringWithFormat:@".%d.%d", major, minor]; systemVersions[2] = [NSString stringWithFormat:@".%d", major]; systemVersions[3] = @""; + NSString *extensions[2]; +#if GTM_IPHONE_SDK + extensions[0] = @".iPhone"; +#else + const NXArchInfo *localInfo = NXGetLocalArchInfo(); + _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); + _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); + extensions[0] = [NSString stringWithFormat:@".%s", genericInfo->name]; +#endif + extensions[1] = @""; + int i,j; // Note that we are searching for the most exact match first. - for (int i = 0; !thePath && i < sizeof(extensions) / sizeof(*extensions); ++i) { - for (int j = 0; !thePath && j < sizeof(systemVersions) / sizeof(*systemVersions); j++) { - NSString *fullName = [NSString stringWithFormat:@"%@%@%@", name, extensions[i], systemVersions[j]]; + for (i = 0; + !thePath && i < sizeof(extensions) / sizeof(*extensions); + ++i) { + for (j = 0; + !thePath && j < sizeof(systemVersions) / sizeof(*systemVersions); + j++) { + NSString *fullName = [NSString stringWithFormat:@"%@%@%@", + name, extensions[i], systemVersions[j]]; thePath = [myBundle pathForResource:fullName ofType:extension]; if (thePath) break; - fullName = [NSString stringWithFormat:@"%@%@%@", name, systemVersions[j], extensions[i]]; + fullName = [NSString stringWithFormat:@"%@%@%@", + name, systemVersions[j], extensions[i]]; thePath = [myBundle pathForResource:fullName ofType:extension]; } } @@ -293,206 +475,387 @@ NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey"; return thePath; } -- (NSString *)saveToPathForFileNamed:(NSString*)name - extension:(NSString*)extension { - NSString *newPath = nil; +- (NSString *)gtm_saveToPathForFileNamed:(NSString*)name + extension:(NSString*)extension { + char const *system; +#if GTM_IPHONE_SDK + system = "iPhone"; +#else const NXArchInfo *localInfo = NXGetLocalArchInfo(); - NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); + _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); + system = genericInfo->name; +#endif long major, minor, bugFix; [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; NSString *fullName = [NSString stringWithFormat:@"%@.%s.%d.%d.%d", - name, localInfo->name, major, minor, bugFix]; + name, system, major, minor, bugFix]; - // Is this build under Pulse? - if ([self isRunningUnderPulse]) { - // Use the Pulse base directory - newPath = [[[self pulseBaseDirectory] - stringByAppendingPathComponent:fullName] - stringByAppendingPathExtension:extension]; - } else { - // Developer build, use their home directory Desktop. - newPath = [[[NSHomeDirectory() - stringByAppendingPathComponent:@"Desktop"] - stringByAppendingPathComponent:fullName] - stringByAppendingPathExtension:extension]; - } - return newPath; + NSString *basePath = [[self class] gtm_getUnitTestSaveToDirectory]; + return [[basePath stringByAppendingPathComponent:fullName] + stringByAppendingPathExtension:extension]; } - + #pragma mark UnitTestImage -// Returns an image containing a representation of the object suitable for use -// in comparing against a master image. -// NB this means that all colors should be device based, as colorsynced colors -// will be different on different devices. -// +// Create a CGColorSpaceRef appropriate for using in creating a unit test image +// iPhone uses device colorspace. // Returns: -// an image of the object -- (NSImage*)unitTestImage { - // Must be overridden by subclasses - [NSException raise:NSInternalInconsistencyException - format:@"%@ must override -%@", - NSStringFromClass([self class]), - NSStringFromSelector(_cmd)]; +// an CGColorSpaceRef of the object. Caller must release +- (CGColorSpaceRef)gtm_createUnitTestColorspace { +#if GTM_IPHONE_SDK + return CGColorSpaceCreateDeviceRGB(); +#else + return CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); +#endif +} - return nil; // appease the compiler +// Create a CGBitmapContextRef appropriate for using in creating a unit test +// image. If data is non-NULL, returns the buffer that the bitmap is +// using for it's underlying storage. You must free this buffer using +// free. If data is NULL, uses it's own internal storage. +// +// Returns: +// an CGContextRef of the object. Caller must release +- (CGContextRef)gtm_createUnitTestBitmapContextOfSize:(CGSize)size + data:(unsigned char**)data { + CGContextRef context = NULL; + size_t height = size.height; + size_t width = size.width; + size_t bytesPerRow = width * 4; + size_t bitsPerComponent = 8; + CGColorSpaceRef cs = [self gtm_createUnitTestColorspace]; + _GTMDevAssert(cs, @"Couldn't create colorspace"); + CGBitmapInfo info = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault; + if (data) { + *data = calloc(bytesPerRow, height); + _GTMDevAssert(*data, @"Couldn't create bitmap"); + } + context = CGBitmapContextCreate(data ? *data : NULL, width, height, + bitsPerComponent, bytesPerRow, cs, info); + _GTMDevAssert(context, @"Couldn't create an context"); + if (!data) { + CGContextClearRect(context, CGRectMake(0, 0, size.width, size.height)); + } + CGContextSetRenderingIntent(context, kCGRenderingIntentRelativeColorimetric); + CGContextSetInterpolationQuality(context, kCGInterpolationNone); + CGContextSetShouldAntialias(context, NO); + CGContextSetAllowsAntialiasing(context, NO); + CGContextSetShouldSmoothFonts(context, NO); + CFRelease(cs); + return context; } // Checks to see that system settings are valid for doing an image comparison. -// The main issue is that we make sure that we are set to using Blue Aqua as -// our appearance and that the scroll arrows are set correctly. -// +// To be overridden by subclasses. // Returns: // YES if we can do image comparisons for this object type. -- (BOOL)areSystemSettingsValidForDoingImage { - NSWorkspace *ws = [NSWorkspace sharedWorkspace]; - BOOL isGood = YES; - - if ([self needsAquaBlueAppearanceForDoingImage] && - ![[ws gtm_themeAppearance] isEqualToString:(NSString *)kThemeAppearanceAquaBlue]) { - NSLog(@"Cannot do image test as appearance is not blue. " - "Please set it in the Appearance System Preference."); - isGood = NO; +- (BOOL)gtm_areSystemSettingsValidForDoingImage { + return YES; +} + +- (NSString *)gtm_imageUTI { +#if GTM_IPHONE_SDK + return (NSString*)kUTTypePNG; +#else + // Currently can't use PNG on Leopard. (10.5.2) + // Radar:5844618 PNG importer/exporter in ImageIO is lossy + return (NSString*)kUTTypeTIFF; +#endif +} + +// Return the extension to be used for saving unittest images +// +// Returns +// An extension (e.g. "png") +- (NSString*)gtm_imageExtension { + NSString *uti = [self gtm_imageUTI]; +#if GTM_IPHONE_SDK + if ([uti isEqualToString:(NSString*)kUTTypePNG]) { + return @"png"; + } else if ([uti isEqualToString:(NSString*)kUTTypeJPEG]) { + return @"jpg"; + } else { + _GTMDevAssert(NO, @"Illegal UTI for iPhone"); } + return nil; +#else + CFStringRef extension = UTTypeCopyPreferredTagWithClass((CFStringRef)uti, + kUTTagClassFilenameExtension); + _GTMDevAssert(extension, @"No extension for uti: %@", uti); - if ([self needsScrollBarArrowsLowerRightForDoingImage] && - [ws gtm_themeScrollBarArrowStyle] != kThemeScrollBarArrowsLowerRight) { - NSLog(@"Cannot do image test as scroll bar arrows are not together" - "bottom right. Please set it in the Appearance System Preference."); - isGood = NO; + return [GTMNSMakeCollectable(extension) autorelease]; +#endif +} + +// Return image data in the format expected for gtm_imageExtension +// So for a "png" extension I would expect "png" data +// +// Returns +// NSData for image +- (NSData*)gtm_imageDataForImage:(CGImageRef)image { + NSData *data = nil; +#if GTM_IPHONE_SDK + // iPhone support + UIImage *uiImage = [UIImage imageWithCGImage:image]; + NSString *uti = [self gtm_imageUTI]; + if ([uti isEqualToString:(NSString*)kUTTypePNG]) { + data = UIImagePNGRepresentation(uiImage); + } else if ([uti isEqualToString:(NSString*)kUTTypeJPEG]) { + data = UIImageJPEGRepresentation(uiImage, 1.0f); + } else { + _GTMDevAssert(NO, @"Illegal UTI for iPhone"); } +#else + data = [NSMutableData data]; + CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data, + (CFStringRef)[self gtm_imageUTI], + 1, + NULL); + // LZW Compression for TIFF + NSDictionary *tiffDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW] + forKey:(NSString*)kCGImagePropertyTIFFCompression]; + NSDictionary *destProps = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithFloat:1.0], + (NSString*)kCGImageDestinationLossyCompressionQuality, + tiffDict, + (NSString*)kCGImagePropertyTIFFDictionary, + nil]; + CGImageDestinationAddImage(dest, image, (CFDictionaryRef)destProps); + CGImageDestinationFinalize(dest); + CFRelease(dest); +#endif + return data; - return isGood; } -// Defaults to the appearance not mattering, individual tests override. -- (BOOL)needsAquaBlueAppearanceForDoingImage { - return NO; -} - -// Defaults to the arrows not mattering, individual tests override. -- (BOOL)needsScrollBarArrowsLowerRightForDoingImage { - return NO; -} - -// Save the unitTestImage to a TIFF file with name |name| at -// ~/Desktop/|name|.tif. The TIFF will be compressed with LZW. +// Save the unitTestImage to an image file with name |name| at +// ~/Desktop/|name|.extension. // // Note: When running under Pulse automation output is redirected to the // Pulse base directory. // // Args: -// name: The name for the TIFF file you would like saved. +// name: The name for the image file you would like saved. // // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToTIFFNamed:(NSString*)name { - NSString *newPath = [self saveToPathForFileNamed:name extension:@"tif"]; - return [self saveToTIFFAt:newPath]; +- (BOOL)gtm_saveToImageNamed:(NSString*)name { + NSString *newPath = [self gtm_saveToPathForImageNamed:name]; + return [self gtm_saveToImageAt:newPath]; } -// Save unitTestImage of |self| to a TIFF file at path |path|. -// The TIFF will be compressed with LZW. +// Save unitTestImage of |self| to an image file at path |path|. // // Args: -// name: The name for the TIFF file you would like saved. +// name: The name for the image file you would like saved. // // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToTIFFAt:(NSString*)path { +- (BOOL)gtm_saveToImageAt:(NSString*)path { if (!path) return NO; - NSData *data = [self TIFFRepresentation]; + NSData *data = [self gtm_imageRepresentation]; return [data writeToFile:path atomically:YES]; } -// Compares unitTestImage of |self| to the TIFF located at |path| -// -// Args: -// path: the path to the TIFF file you want to compare against. -// -// Returns: -// YES if they are equal, NO is they are not +// Generates a CGImageRef from the image at |path| +// Args: +// path: The path to the image. // -- (BOOL)compareWithTIFFNamed:(NSString*)name { - NSString *path = [self pathForTIFFNamed:name]; - return [self compareWithTIFFAt:path]; +// Returns: +// A CGImageRef that you own, or nil if no image at path +- (CGImageRef)gtm_createImageUsingPath:(NSString*)path { + CGImageRef imageRef = nil; +#if GTM_IPHONE_SDK + UIImage *image = [UIImage imageWithContentsOfFile:path]; + if (image) { + imageRef = CGImageRetain(image.CGImage); + } +#else + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)path, + kCFURLPOSIXPathStyle, NO); + if (url) { + CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL); + CFRelease(url); + if (imageSource) { + imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); + CFRelease(imageSource); + } + } +#endif + return imageRef; } -// Compares unitTestImage of |self| to the TIFF located at |path| +/// Compares unitTestImage of |self| to the image located at |path| // // Args: -// path: the path to the TIFF file you want to compare against. +// path: the path to the image file you want to compare against. +// If diff is non-nil, it will contain an auto-released diff of the images. // // Returns: // YES if they are equal, NO is they are not +// If diff is non-nil, it will contain an auto-released diff of the images. // -- (BOOL)compareWithTIFFAt:(NSString*)path { +- (BOOL)gtm_compareWithImageAt:(NSString*)path diffImage:(CGImageRef*)diff { BOOL answer = NO; - NSData *fileData = [NSData dataWithContentsOfFile:path]; - if (fileData) { - NSData *imageData = [self TIFFRepresentation]; - if (imageData) { - NSBitmapImageRep *fileRep = [NSBitmapImageRep imageRepWithData:fileData]; - if (fileRep) { - NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData]; - if (imageRep) { - NSSize fileSize = [fileRep size]; - NSSize imageSize = [imageRep size]; - if (NSEqualSizes(fileSize,imageSize)) { - // if all the sizes are equal, run through the bytes and compare - // them for equality. - answer = YES; - for (int row = 0; row < fileSize.height; row++) { - for (int col = 0; col < fileSize.width && answer == YES; col++) { - NSColor *imageColor = [imageRep colorAtX:col y:row]; - NSColor *fileColor = [fileRep colorAtX:col y:row]; - - answer = [imageColor isEqual:fileColor]; - } - } + if (diff) { + *diff = nil; + } + CGImageRef fileRep = [self gtm_createImageUsingPath:path]; + _GTMDevAssert(fileRep, @"Unable to create imagerep from %@", path); + + CGImageRef imageRep = [self gtm_createUnitTestImage]; + _GTMDevAssert(imageRep, @"Unable to create imagerep for %@", self); + + size_t fileHeight = CGImageGetHeight(fileRep); + size_t fileWidth = CGImageGetWidth(fileRep); + size_t imageHeight = CGImageGetHeight(imageRep); + size_t imageWidth = CGImageGetWidth(imageRep); + if (fileHeight == imageHeight && fileWidth == imageWidth) { + // if all the sizes are equal, run through the bytes and compare + // them for equality. + // Do an initial fast check, if this fails and the caller wants a + // diff, we'll do the slow path and create the diff. The diff path + // could be optimized, but probably not necessary at this point. + answer = YES; + + CGSize imageSize = CGSizeMake(fileWidth, fileHeight); + CGRect imageRect = CGRectMake(0, 0, fileWidth, fileHeight); + unsigned char *fileData; + unsigned char *imageData; + CGContextRef fileContext = [self gtm_createUnitTestBitmapContextOfSize:imageSize + data:&fileData]; + _GTMDevAssert(fileContext, @"Unable to create filecontext"); + CGContextDrawImage(fileContext, imageRect, fileRep); + CGContextRef imageContext =[self gtm_createUnitTestBitmapContextOfSize:imageSize + data:&imageData]; + _GTMDevAssert(imageContext, @"Unable to create imageContext"); + CGContextDrawImage(imageContext, imageRect, imageRep); + + size_t fileBytesPerRow = CGBitmapContextGetBytesPerRow(fileContext); + size_t imageBytesPerRow = CGBitmapContextGetBytesPerRow(imageContext); + size_t row, col; + + _GTMDevAssert(imageWidth * 4 <= imageBytesPerRow, + @"We expect image data to be 32bit RGBA"); + + for (row = 0; row < fileHeight && answer; row++) { + answer = memcmp(fileData + fileBytesPerRow * row, + imageData + imageBytesPerRow * row, + imageWidth * 4) == 0; + } + if (!answer && diff) { + answer = YES; + unsigned char *diffData; + CGContextRef diffContext = [self gtm_createUnitTestBitmapContextOfSize:imageSize + data:&diffData]; + _GTMDevAssert(diffContext, @"Can't make diff context"); + size_t diffRowBytes = CGBitmapContextGetBytesPerRow(diffContext); + for (row = 0; row < imageHeight; row++) { + unsigned long *imageRow = (unsigned long*)(imageData + imageBytesPerRow * row); + unsigned long *fileRow = (unsigned long*)(fileData + fileBytesPerRow * row); + unsigned long* diffRow = (unsigned long*)(diffData + diffRowBytes * row); + for (col = 0; col < imageWidth; col++) { + unsigned long imageColor = imageRow[col]; + unsigned long fileColor = fileRow[col]; + + unsigned char imageAlpha = imageColor & 0xF; + unsigned char imageBlue = imageColor >> 8 & 0xF; + unsigned char imageGreen = imageColor >> 16 & 0xF; + unsigned char imageRed = imageColor >> 24 & 0xF; + unsigned char fileAlpha = fileColor & 0xF; + unsigned char fileBlue = fileColor >> 8 & 0xF; + unsigned char fileGreen = fileColor >> 16 & 0xF; + unsigned char fileRed = fileColor >> 24 & 0xF; + + // Check to see if color is almost right. + // No matter how hard I've tried, I've still gotten occasionally + // screwed over by colorspaces not mapping correctly, and small + // sampling errors coming in. This appears to work for most cases. + // Almost equal is defined to check within 1% on all components. + BOOL equal = almostEqual(imageRed, fileRed) && + almostEqual(imageGreen, fileGreen) && + almostEqual(imageBlue, fileBlue) && + almostEqual(imageAlpha, fileAlpha); + answer &= equal; + if (diff) { + unsigned long newColor; + if (equal) { + newColor = (((unsigned long)imageRed) << 24) + + (((unsigned long)imageGreen) << 16) + + (((unsigned long)imageBlue) << 8) + + (((unsigned long)imageAlpha) / 2); + } else { + newColor = 0xFF0000FF; + } + diffRow[col] = newColor; } } } - } + *diff = CGBitmapContextCreateImage(diffContext); + free(diffData); + CFRelease(diffContext); + free(fileData); + CFRelease(fileContext); + free(imageData); + CFRelease(imageContext); + } + CFRelease(imageRep); + CFRelease(fileRep); } return answer; } - -// Find the path for a TIFF by name in your bundle. -// Do not include the ".tif" extension on your name. + +// Find the path for an image by name in your bundle. +// Do not include the extension on your name. // // Args: -// name: The name for the TIFF file you would like to find. +// name: The name for the image file you would like to find. // // Returns: -// the path if the TIFF exists in your bundle -// or nil if no TIFF to be found +// the path if the image exists in your bundle +// or nil if no image to be found // -- (NSString *)pathForTIFFNamed:(NSString*)name { - return [self pathForFileNamed:name extension:@"tif"]; +- (NSString *)gtm_pathForImageNamed:(NSString*)name { + return [self gtm_pathForFileNamed:name + extension:[self gtm_imageExtension]]; +} + +- (NSString *)gtm_saveToPathForImageNamed:(NSString*)name { + return [self gtm_saveToPathForFileNamed:name + extension:[self gtm_imageExtension]]; } -// Gives us a LZW compressed representation of unitTestImage of |self|. +// Gives us a representation of unitTestImage of |self|. // // Returns: -// a LZW compressed TIFF if successful +// a representation of image if successful // nil if failed // -- (NSData *)TIFFRepresentation { - NSImage *image = [self unitTestImage]; - // factor is ignored unless compression style is NSJPEGCompression - return [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0f]; +- (NSData *)gtm_imageRepresentation { + CGImageRef imageRep = [self gtm_createUnitTestImage]; + NSData *data = [self gtm_imageDataForImage:imageRep]; + _GTMDevAssert(data, @"unable to create %@ from %@", [self gtm_imageExtension], self); + CFRelease(imageRep); + return data; } #pragma mark UnitTestState -static NSString* const kGTMStateFileExtension = @"gtmUTState"; +// Return the extension to be used for saving unittest states +// +// Returns +// An extension (e.g. "gtmUTState") +- (NSString*)gtm_stateExtension { + return @"gtmUTState"; +} -// Save the encoded unit test state to a .gtmUTState file with name |name| at -// ~/Desktop/|name|.gtmUTState. +// Save the encoded unit test state to a state file with name |name| at +// ~/Desktop/|name|.extension. // // Note: When running under Pulse automation output is redirected to the // Pulse base directory. @@ -503,13 +866,12 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToStateNamed:(NSString*)name { - NSString *newPath = [self saveToPathForFileNamed:name - extension:kGTMStateFileExtension]; - return [self saveToStateAt:newPath]; +- (BOOL)gtm_saveToStateNamed:(NSString*)name { + NSString *newPath = [self gtm_saveToPathForStateNamed:name]; + return [self gtm_saveToStateAt:newPath]; } -// Save encoded unit test state of |self| to a .gtmUTState file at path |path|. +// Save encoded unit test state of |self| to a state file at path |path|. // // Args: // name: The name for the state file you would like saved. @@ -517,27 +879,13 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // Returns: // YES if the file was successfully saved. // -- (BOOL)saveToStateAt:(NSString*)path { +- (BOOL)gtm_saveToStateAt:(NSString*)path { if (!path) return NO; - NSDictionary *dictionary = [self stateRepresentation]; + NSDictionary *dictionary = [self gtm_stateRepresentation]; return [dictionary writeToFile:path atomically:YES]; } -// Compares encoded unit test state of |self| to the .gtmUTState named |name| -// -// Args: -// name: the name of the state file you want to compare against. -// -// Returns: -// YES if they are equal, NO is they are not -// -- (BOOL)compareWithStateNamed:(NSString*)name { - NSString *path = [self pathForStateNamed:name]; - return [self compareWithStateAt:path]; - -} - -// Compares encoded unit test state of |self| to the .gtmUTState located at +// Compares encoded unit test state of |self| to the state file located at // |path| // // Args: @@ -546,15 +894,15 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // Returns: // YES if they are equal, NO is they are not // -- (BOOL)compareWithStateAt:(NSString*)path { +- (BOOL)gtm_compareWithStateAt:(NSString*)path { NSDictionary *masterDict = [NSDictionary dictionaryWithContentsOfFile:path]; - NSAssert1(masterDict, @"Unable to create dictionary from %@", path); - NSDictionary *selfDict = [self stateRepresentation]; - return [selfDict isEqualTo: masterDict]; + _GTMDevAssert(masterDict, @"Unable to create dictionary from %@", path); + NSDictionary *selfDict = [self gtm_stateRepresentation]; + return [selfDict isEqual: masterDict]; } // Find the path for a state by name in your bundle. -// Do not include the ".gtmUTState" extension. +// Do not include the extension. // // Args: // name: The name for the state file you would like to find. @@ -563,8 +911,13 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // the path if the state exists in your bundle // or nil if no state to be found // -- (NSString *)pathForStateNamed:(NSString*)name { - return [self pathForFileNamed:name extension:kGTMStateFileExtension]; +- (NSString *)gtm_pathForStateNamed:(NSString*)name { + return [self gtm_pathForFileNamed:name extension:[self gtm_stateExtension]]; +} + +- (NSString *)gtm_saveToPathForStateNamed:(NSString*)name { + return [self gtm_saveToPathForFileNamed:name + extension:[self gtm_stateExtension]]; } // Gives us the encoded unit test state |self| @@ -573,12 +926,13 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // the encoded state if successful // nil if failed // -- (NSDictionary *)stateRepresentation { +- (NSDictionary *)gtm_stateRepresentation { NSDictionary *dictionary = nil; if ([self conformsToProtocol:@protocol(GTMUnitTestingEncoding)]) { id<GTMUnitTestingEncoding> encoder = (id<GTMUnitTestingEncoding>)self; - GTMUnitTestingKeyedCoder *archiver = [[[GTMUnitTestingKeyedCoder alloc] init] autorelease]; - [encoder unitTestEncodeState:archiver]; + GTMUnitTestingKeyedCoder *archiver; + archiver = [[[GTMUnitTestingKeyedCoder alloc] init] autorelease]; + [encoder gtm_unitTestEncodeState:archiver]; dictionary = [archiver dictionary]; } return dictionary; @@ -591,140 +945,20 @@ static NSString* const kGTMStateFileExtension = @"gtmUTState"; // // Arguments: // inCoder - the coder to encode our state into -- (void)unitTestEncodeState:(NSCoder*)inCoder { - // Currently does nothing, but all impls of unitTestEncodeState - // should be calling [super unitTestEncodeState] as their first action. -} - -- (NSMutableArray*)unitTestExposedBindingsToIgnore { - NSMutableArray *array; - if ([[self exposedBindings] containsObject:NSFontBinding]) { - array = [NSMutableArray arrayWithObjects: - NSFontBoldBinding, NSFontFamilyNameBinding, NSFontItalicBinding, - NSFontNameBinding, NSFontSizeBinding, nil]; - } else { - array = [NSMutableArray array]; - } - return array; -} - -- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding { - // Always test identity - id value = [self valueForKey:binding]; - if (!value) { - value = [NSNull null]; - } - NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:value - forKey: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. +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + // All impls of gtm_unitTestEncodeState + // should be calling [super gtm_unitTestEncodeState] as their first action. + _GTMDevAssert([inCoder isKindOfClass:[GTMUnitTestingKeyedCoder class]], + @"Coder must be of kind GTMUnitTestingKeyedCoder"); - // If anybody can think of more to add, please go nuts. - if ([binding isEqualToString:NSAlignmentBinding]) { - [dict setObjectAndKey:[NSNumber numberWithInt:NSLeftTextAlignment]]; - [dict setObjectAndKey:[NSNumber numberWithInt:NSRightTextAlignment]]; - [dict setObjectAndKey:[NSNumber numberWithInt:NSCenterTextAlignment]]; - [dict setObjectAndKey:[NSNumber numberWithInt:NSJustifiedTextAlignment]]; - NSNumber *natural = [NSNumber numberWithInt:NSNaturalTextAlignment]; - [dict 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 setObjectAndKey:[NSImage imageNamed:@"NSApplicationIcon"]]; - [dict setObjectAndKey:[NSNull null]]; - } else if ([binding isEqualToString:NSAnimateBinding] || - [binding isEqualToString:NSDocumentEditedBinding] || - [binding isEqualToString:NSEditableBinding] || - [binding isEqualToString:NSEnabledBinding] || - [binding isEqualToString:NSHiddenBinding] || - [binding isEqualToString:NSVisibleBinding]) { - // This handles all bool value bindings - [dict setObjectAndKey:[NSNumber numberWithBool:YES]]; - [dict setObjectAndKey:[NSNumber numberWithBool:NO]]; - } else if ([binding isEqualToString:NSAlternateTitleBinding] || - [binding isEqualToString:NSHeaderTitleBinding] || - [binding isEqualToString:NSLabelBinding] || - [binding isEqualToString:NSRepresentedFilenameBinding] || - [binding isEqualToString:NSTitleBinding] || - [binding isEqualToString:NSToolTipBinding]) { - // This handles all string value bindings - [dict setObjectAndKey:@"happy"]; - [dict setObjectAndKey:[NSNull null]]; - // Test some non-ascii roman text - char a_not_alpha[] = { 'A', 0xE2, 0x89, 0xA2, 0xCE, 0x91, '.', 0x00 }; - [dict setObjectAndKey:[NSString stringWithUTF8String:a_not_alpha]]; - // Test some korean - char hangugo[] - = { 0xED, 0x95, 0x9C, 0xEA, 0xB5, 0xAD, 0xEC, 0x96, 0xB4, 0x00 }; - [dict setObjectAndKey:[NSString stringWithUTF8String:hangugo]]; - // Test some japanese - char nihongo[] - = { 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E, 0x00 }; - [dict setObjectAndKey:[NSString stringWithUTF8String:nihongo]]; - // Test some arabic (right to left baby! ;-) - char arabic[] = { 0xd9, 0x83, 0xd8, 0xa7, 0xd9, 0x83, 0xd8, 0xa7, 0x00 }; - [dict setObjectAndKey:[NSString stringWithUTF8String:arabic]]; - } else if ([binding isEqualToString:NSMaximumRecentsBinding] || - [binding isEqualToString:NSMaxValueBinding] || - [binding isEqualToString:NSMaxWidthBinding] || - [binding isEqualToString:NSMinValueBinding] || - [binding isEqualToString:NSMinWidthBinding] || - [binding isEqualToString:NSRecentSearchesBinding] || - [binding isEqualToString:NSRowHeightBinding] || - [binding isEqualToString:NSWidthBinding]) { - // This handles all int value bindings - [dict setObjectAndKey:[NSNumber numberWithInt:0]]; - [dict setObjectAndKey:[NSNumber numberWithInt:-1]]; - [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MAX]]; - [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MIN]]; - } else if ([binding isEqualToString:NSTextColorBinding]) { - // This handles all color value bindings - [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:1.0]]; - [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.0]]; - [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.5]]; - [dict setObjectAndKey:[NSColor colorWithCalibratedRed:0.5 green:0.5 blue:0.5 alpha:0.5]]; - [dict 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 setObjectAndKey:[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]]; - [dict setObjectAndKey:[NSFont toolTipsFontOfSize:[NSFont smallSystemFontSize]]]; - [dict setObjectAndKey:[NSFont labelFontOfSize:144.0]]; + // If the object has a delegate, give it a chance to respond + if ([self respondsToSelector:@selector(delegate)]) { + id delegate = [self performSelector:@selector(delegate)]; + if (delegate && + [delegate respondsToSelector:@selector(gtm_unitTestEncoderWillEncode:inCoder:)]) { + [delegate gtm_unitTestEncoderWillEncode:self inCoder:inCoder]; + } } - return dict; -} - -@end - -@implementation NSMutableDictionary (GTMUnitTestingAdditions) -// Sets an object and a key to the same value in a dictionary. -- (void)setObjectAndKey:(id)objectAndKey { - [self setObject:objectAndKey forKey:objectAndKey]; -} -@end - - -@implementation NSObject (GTMUnitTestingPulseAdditions) - -- (BOOL)isRunningUnderPulse { - - if ([[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BUILD_NUMBER"]) return YES; - return NO; - -} - -- (NSString *)pulseBaseDirectory { - - return [[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BASE_DIR"]; - } @end - - diff --git a/UnitTesting/GTMNSView+UnitTesting.m b/UnitTesting/GTMNSView+UnitTesting.m deleted file mode 100644 index dd3e423..0000000 --- a/UnitTesting/GTMNSView+UnitTesting.m +++ /dev/null @@ -1,135 +0,0 @@ -// -// GTMNSView+UnitTesting.m -// -// Category for making unit testing of graphics/UI easier. -// Allows you to save a view out to a TIFF file, and compare a view -// with a previously stored representation to make sure it hasn't changed. -// -// 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 "GTMNSView+UnitTesting.h" -#import <SenTestingKit/SenTestingKit.h> -#import "GTMGeometryUtils.h" - -// A view that allows you to delegate out drawing using the formal -// GTMUnitTestViewDelegate protocol above. This is useful when writing up unit -// tests for visual elements. -// Your test will often end up looking like this: -// - (void)testFoo { -// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); -// } -// and your testSuite will also implement the unitTestViewDrawRect method to do -// it's actual drawing. The above creates a view of size 200x200 that draws -// it's content using |self|'s unitTestViewDrawRect method and compares it to -// the contents of the file Foo.tif to make sure it's valid -@implementation GTMUnitTestView - -- (id)initWithFrame:(NSRect)frame drawer:(id<GTMUnitTestViewDrawer>)drawer contextInfo:(void*)contextInfo{ - self = [super initWithFrame:frame]; - if (self != nil) { - drawer_ = [drawer retain]; - contextInfo_ = contextInfo; - } - return self; -} - -- (void) dealloc { - [drawer_ release]; - [super dealloc]; -} - - -- (void)drawRect:(NSRect)rect { - [drawer_ unitTestViewDrawRect:rect contextInfo:contextInfo_]; -} - - -@end - -@implementation NSView (GTMUnitTestingAdditions) - -// Returns an image containing a representation of the object -// suitable for use in comparing against a master image. -// NB this means that all colors should be from "NSDevice" color space -// Does all of it's drawing with smoothfonts and antialiasing off -// to avoid issues with font smoothing settings and antialias differences -// between ppc and x86. -// -// Returns: -// an image of the object -- (NSImage*)unitTestImage { - // Create up a context - NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:[self bounds]]; - NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; - - // Store Current Context and switch to bitmap context - [NSGraphicsContext saveGraphicsState]; - [NSGraphicsContext setCurrentContext: bitmapContext]; - CGContextRef contextRef = (CGContextRef)[bitmapContext graphicsPort]; - - // Save our state and turn off font smoothing and antialias. - CGContextSaveGState(contextRef); - CGContextSetShouldSmoothFonts(contextRef, false); - CGContextSetShouldAntialias(contextRef, false); - CGContextClearRect(contextRef, GTMNSRectToCGRect([self bounds])); - [self displayRectIgnoringOpacity:[self bounds] inContext:bitmapContext]; - - // Clean up and create image - CGContextRestoreGState(contextRef); - [NSGraphicsContext restoreGraphicsState]; - NSImage *image = [[[NSImage alloc] init] autorelease]; - [image addRepresentation:imageRep]; - return image; -} - -// Returns whether unitTestEncodeState should recurse into subviews -// of a particular view. -// Dan Waylonis discovered that if you have "Full keyboard access" in the -// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes -// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the -// case of NSTextFields we don't want to recurse into their subviews. There may -// be other cases like this, so instead of specializing unitTestEncodeState: to -// look for NSTextFields, NSTextFields will just not allow us to recurse into -// their subviews. -// -// Returns: -// should unitTestEncodeState pick up subview state. -- (BOOL)shouldEncodeStateRecurseIntoSubviews { - return YES; -} - -// Encodes the state of an object in a manner suitable for comparing -// against a master state file so we can determine whether the -// object is in a suitable state. -// -// Arguments: -// inCoder - the coder to encode our state into -- (void)unitTestEncodeState:(NSCoder*)inCoder { - [super unitTestEncodeState:inCoder]; - [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"]; - if ([self shouldEncodeStateRecurseIntoSubviews]) { - NSEnumerator *subviewEnum = [[self subviews] objectEnumerator]; - NSView *subview = nil; - int i = 0; - while ((subview = [subviewEnum nextObject])) { - [inCoder encodeObject:subview forKey:[NSString stringWithFormat:@"ViewSubView %d", i]]; - i = i + 1; - } - } -} - -@end - diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h index 75adc13..758f6d4 100644 --- a/UnitTesting/GTMSenTestCase.h +++ b/UnitTesting/GTMSenTestCase.h @@ -16,11 +16,49 @@ // the License. // +// Portions of this file fall under the following license, marked with +// SENTE_BEGIN - SENTE_END +// +// Copyright (c) 1997-2005, Sen:te (Sente SA). All rights reserved. +// +// Use of this source code is governed by the following license: +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL Sente SA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Note: this license is equivalent to the FreeBSD license. +// +// This notice may not be removed from this file. + // Some extra test case macros that would have been convenient for SenTestingKit // to provide. I didn't stick GTM in front of the Macro names, so that they would // be easy to remember. +#import "GTMDefines.h" + +#if (!GTM_IPHONE_SDK) #import <SenTestingKit/SenTestingKit.h> +#else +#import <Foundation/Foundation.h> +NSString *STComposeString(NSString *, ...); +#endif // Generates a failure when a1 != noErr // Args: @@ -151,36 +189,34 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEquals(a1, a2, description, ...) \ do { \ - @try {\ - if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + @try {\ + if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ - } \ - else { \ - typeof(a1) a1value = (a1); \ - typeof(a2) a2value = (a2); \ - NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(typeof(a1))]; \ - NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(typeof(a2))]; \ - if ([a1encoded isEqualToValue:a2encoded]) { \ + } else { \ + __typeof__(a1) a1value = (a1); \ + __typeof__(a2) a2value = (a2); \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \ + if ([a1encoded isEqualToValue:a2encoded]) { \ NSString *_expression = [NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2]; \ if (description) { \ _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:_expression]]; \ - } \ - } \ + withDescription:_expression]]; \ + } \ } \ - @catch (id anException) {\ - [self failWithException:[NSException \ - failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + } \ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ } while(0) // Generates a failure when a1 is equal to a2. This test is for objects. @@ -195,8 +231,8 @@ do { \ @try {\ id a1value = (a1); \ id a2value = (a2); \ - if ( (@encode(typeof(a1value)) == @encode(id)) && \ - (@encode(typeof(a2value)) == @encode(id)) && \ + if ( (@encode(__typeof__(a1value)) == @encode(id)) && \ + (@encode(__typeof__(a2value)) == @encode(id)) && \ ![(id)a1value isEqual:(id)a2value] ) continue; \ NSString *_expression = [NSString stringWithFormat:@"%s('%@') != %s('%@')", #a1, [a1 description], #a2, [a2 description]]; \ if (desc) { \ @@ -226,34 +262,33 @@ do { \ #define STAssertOperation(a1, a2, op, description, ...) \ do { \ @try {\ - if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ - } \ - else { \ - typeof(a1) a1value = (a1); \ - typeof(a2) a2value = (a2); \ - if (!(a1value op a2value)) { \ + } else { \ + __typeof__(a1) a1value = (a1); \ + __typeof__(a2) a2value = (a2); \ + if (!(a1value op a2value)) { \ double a1DoubleValue = a1value; \ double a2DoubleValue = a2value; \ NSString *_expression = [NSString stringWithFormat:@"%s (%lg) %s %s (%lg)", #a1, a1DoubleValue, #op, #a2, a2DoubleValue]; \ if (description) { \ _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:_expression]]; \ - } \ - } \ + withDescription:_expression]]; \ + } \ + } \ } \ @catch (id anException) {\ - [self failWithException:[NSException \ - failureInRaise:[NSString stringWithFormat:@"(%s) %s (%s)", #a1, #op, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + [self failWithException:[NSException \ + failureInRaise:[NSString stringWithFormat:@"(%s) %s (%s)", #a1, #op, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ }\ } while(0) @@ -314,27 +349,27 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertEqualStrings(a1, a2, description, ...) \ do { \ - @try {\ - id a1value = (a1); \ - id a2value = (a2); \ - if (a1value == a2value) continue; \ - if ([a1value isKindOfClass:[NSString class]] && \ - [a2value isKindOfClass:[NSString class]] && \ - [a1value compare:a2value options:0] == NSOrderedSame) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ - andObject: a2value \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - } while(0) + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if (a1value == a2value) continue; \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] == NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) // Generates a failure when string a1 is equal to string a2. This call // differs from STAssertEqualObjects in that strings that are different in @@ -349,26 +384,26 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEqualStrings(a1, a2, description, ...) \ do { \ - @try {\ - id a1value = (a1); \ - id a2value = (a2); \ - if ([a1value isKindOfClass:[NSString class]] && \ - [a2value isKindOfClass:[NSString class]] && \ - [a1value compare:a2value options:0] != NSOrderedSame) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ - andObject: a2value \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - } while(0) + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] != NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) // Generates a failure when c-string a1 is not equal to c-string a2. // Args: @@ -379,25 +414,25 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertEqualCStrings(a1, a2, description, ...) \ do { \ - @try {\ - const char* a1value = (a1); \ - const char* a2value = (a2); \ - if (a1value == a2value) continue; \ - if (strcmp(a1value, a2value) == 0) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ - andObject: [NSString stringWithUTF8String:a2value] \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - } while(0) + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (a1value == a2value) continue; \ + if (strcmp(a1value, a2value) == 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) // Generates a failure when c-string a1 is equal to c-string a2. // Args: @@ -408,22 +443,552 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEqualCStrings(a1, a2, description, ...) \ do { \ - @try {\ - const char* a1value = (a1); \ - const char* a2value = (a2); \ - if (strcmp(a1value, a2value) != 0) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ - andObject: [NSString stringWithUTF8String:a2value] \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - } while(0) + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (strcmp(a1value, a2value) != 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +#if GTM_IPHONE_SDK + +// SENTE_BEGIN +/*" Generates a failure when !{ [a1 isEqualTo:a2] } is false + (or one is nil and the other is not). + _{a1 The object on the left.} + _{a2 The object on the right.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertEqualObjects(a1, a2, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if (a1value == a2value) continue; \ + if ( (@encode(__typeof__(a1value)) == @encode(id)) && \ + (@encode(__typeof__(a2value)) == @encode(id)) && \ + [(id)a1value isEqual: (id)a2value] ) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + +/*" Generates a failure when a1 is not equal to a2. This test is for + C scalars, structs and unions. + _{a1 The argument on the left.} + _{a2 The argument on the right.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertEquals(a1, a2, description, ...) \ +do { \ + @try {\ + if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } else { \ + __typeof__(a1) a1value = (a1); \ + __typeof__(a2) a2value = (a2); \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType: @encode(__typeof__(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType: @encode(__typeof__(a2))]; \ + if (![a1encoded isEqualToValue:a2encoded]) { \ + [self failWithException:[NSException failureInEqualityBetweenValue: a1encoded \ + andValue: a2encoded \ + withAccuracy: nil \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +#define STAbsoluteDifference(left,right) (MAX(left,right)-MIN(left,right)) + + +/*" Generates a failure when a1 is not equal to a2 within + or - accuracy is false. + This test is for scalars such as floats and doubles where small differences + could make these items not exactly equal, but also works for all scalars. + _{a1 The scalar on the left.} + _{a2 The scalar on the right.} + _{accuracy The maximum difference between a1 and a2 for these values to be + considered equal.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ + +#define STAssertEqualsWithAccuracy(a1, a2, accuracy, description, ...) \ +do { \ + @try {\ + if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } else { \ + __typeof__(a1) a1value = (a1); \ + __typeof__(a2) a2value = (a2); \ + __typeof__(accuracy) accuracyvalue = (accuracy); \ + if (STAbsoluteDifference(a1value, a2value) > accuracyvalue) { \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \ + NSValue *accuracyencoded = [NSValue value:&accuracyvalue withObjCType:@encode(__typeof__(accuracy))]; \ + [self failWithException:[NSException failureInEqualityBetweenValue: a1encoded \ + andValue: a2encoded \ + withAccuracy: accuracyencoded \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + + +/*" Generates a failure unconditionally. + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STFail(description, ...) \ +[self failWithException:[NSException failureInFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]] + + + +/*" Generates a failure when a1 is not nil. + _{a1 An object.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertNil(a1, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + if (a1value != nil) { \ + NSString *_a1 = [NSString stringWithUTF8String: #a1]; \ + NSString *_expression = [NSString stringWithFormat:@"((%@) == nil)", _a1]; \ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: NO \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == nil fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + +/*" Generates a failure when a1 is nil. + _{a1 An object.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertNotNil(a1, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + if (a1value == nil) { \ + NSString *_a1 = [NSString stringWithUTF8String: #a1]; \ + NSString *_expression = [NSString stringWithFormat:@"((%@) != nil)", _a1]; \ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: NO \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != nil fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + +/*" Generates a failure when expression evaluates to false. + _{expr The expression that is tested.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertTrue(expr, description, ...) \ +do { \ + BOOL _evaluatedExpression = (expr);\ + if (!_evaluatedExpression) {\ + NSString *_expression = [NSString stringWithUTF8String: #expr];\ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: NO \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ +} while (0) + + +/*" Generates a failure when expression evaluates to false and in addition will + generate error messages if an exception is encountered. + _{expr The expression that is tested.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertTrueNoThrow(expr, description, ...) \ +do { \ + @try {\ + BOOL _evaluatedExpression = (expr);\ + if (!_evaluatedExpression) {\ + NSString *_expression = [NSString stringWithUTF8String: #expr];\ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: NO \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) ", #expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while (0) + + +/*" Generates a failure when the expression evaluates to true. + _{expr The expression that is tested.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertFalse(expr, description, ...) \ +do { \ + BOOL _evaluatedExpression = (expr);\ + if (_evaluatedExpression) {\ + NSString *_expression = [NSString stringWithUTF8String: #expr];\ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: YES \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ +} while (0) + + +/*" Generates a failure when the expression evaluates to true and in addition + will generate error messages if an exception is encountered. + _{expr The expression that is tested.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertFalseNoThrow(expr, description, ...) \ +do { \ + @try {\ + BOOL _evaluatedExpression = (expr);\ + if (_evaluatedExpression) {\ + NSString *_expression = [NSString stringWithUTF8String: #expr];\ + [self failWithException:[NSException failureInCondition: _expression \ + isTrue: YES \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"!(%s) ", #expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while (0) + + +/*" Generates a failure when expression does not throw an exception. + _{expression The expression that is evaluated.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent. +"*/ +#define STAssertThrows(expr, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (id anException) { \ + continue; \ + }\ + [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: nil \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ +} while (0) + + +/*" Generates a failure when expression does not throw an exception of a + specific class. + _{expression The expression that is evaluated.} + _{specificException The specified class of the exception.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertThrowsSpecific(expr, specificException, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (specificException *anException) { \ + continue; \ + }\ + @catch (id anException) {\ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + continue; \ + }\ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: nil \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ +} while (0) + + +/*" Generates a failure when expression does not throw an exception of a + specific class with a specific name. Useful for those frameworks like + AppKit or Foundation that throw generic NSException w/specific names + (NSInvalidArgumentException, etc). + _{expression The expression that is evaluated.} + _{specificException The specified class of the exception.} + _{aName The name of the specified exception.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} + +"*/ +#define STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (specificException *anException) { \ + if ([aName isEqualToString: [anException name]]) continue; \ + NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description);\ + [self failWithException: \ + [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + continue; \ + }\ + @catch (id anException) {\ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + [self failWithException: \ + [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + continue; \ + }\ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + [self failWithException: \ + [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: nil \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ +} while (0) + + +/*" Generates a failure when expression does throw an exception. + _{expression The expression that is evaluated.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertNoThrow(expr, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while (0) + + +/*" Generates a failure when expression does throw an exception of the specitied + class. Any other exception is okay (i.e. does not generate a failure). + _{expression The expression that is evaluated.} + _{specificException The specified class of the exception.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ +#define STAssertNoThrowSpecific(expr, specificException, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (specificException *anException) { \ + [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anythingElse) {\ + ; \ + }\ +} while (0) + + +/*" Generates a failure when expression does throw an exception of a + specific class with a specific name. Useful for those frameworks like + AppKit or Foundation that throw generic NSException w/specific names + (NSInvalidArgumentException, etc). + _{expression The expression that is evaluated.} + _{specificException The specified class of the exception.} + _{aName The name of the specified exception.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} + +"*/ +#define STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...) \ +do { \ + @try { \ + (expr);\ + } \ + @catch (specificException *anException) { \ + if ([aName isEqualToString: [anException name]]) { \ + NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description);\ + [self failWithException: \ + [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ + exception: anException \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + } \ + continue; \ + }\ + @catch (id anythingElse) {\ + ; \ + }\ +} while (0) + + + +@interface NSException (GTMSenTestAdditions) ++ (NSException *)failureInFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInCondition:(NSString *)condition + isTrue:(BOOL)isTrue + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInEqualityBetweenObject:(id)left + andObject:(id)right + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInEqualityBetweenValue:(NSValue *)left + andValue:(NSValue *)right + withAccuracy:(NSValue *)accuracy + inFile:(NSString *)filename + atLine:(int) ineNumber + withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInRaise:(NSString *)expression + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInRaise:(NSString *)expression + exception:(NSException *)exception + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ...; +@end + +// SENTE_END + +@interface NSObject (GTMSenTestAdditions) +- (void)failWithException:(NSException*)exception; +@end + +@interface SenTestCase : NSObject +- (void) setUp; +- (void) tearDown; +@end + +CF_EXPORT NSString * const SenTestFailureException; + +#endif // GTM_IPHONE_SDK diff --git a/UnitTesting/GTMSenTestCase.m b/UnitTesting/GTMSenTestCase.m new file mode 100644 index 0000000..9b6cf17 --- /dev/null +++ b/UnitTesting/GTMSenTestCase.m @@ -0,0 +1,140 @@ +// +// GTMSenTestCase.m +// +// Copyright 2007-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 "GTMSenTestCase.h" + +#if GTM_IPHONE_SDK + +#import <stdarg.h> + +@implementation NSException (GTMSenTestAdditions) + ++ (NSException *)failureInFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"%@:%d: error: %@", filename, lineNumber, reason]; + return [NSException exceptionWithName:SenTestFailureException + reason:reason + userInfo:nil]; +} + ++ (NSException *)failureInCondition:(NSString *)condition + isTrue:(BOOL)isTrue + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"condition '%@' is %s : %@", + condition, isTrue ? "TRUE" : "FALSE", reason]; + return [self failureInFile:filename atLine:lineNumber withDescription:reason]; +} + ++ (NSException *)failureInEqualityBetweenObject:(id)left + andObject:(id)right + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"%@ != %@ : %@", + left, right, reason]; + return [self failureInFile:filename atLine:lineNumber withDescription:reason]; +} + ++ (NSException *)failureInEqualityBetweenValue:(NSValue *)left + andValue:(NSValue *)right + withAccuracy:(NSValue *)accuracy + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"%@ != %@ with accuracy %@ : %@", + left, right, accuracy, reason]; + return [self failureInFile:filename atLine:lineNumber withDescription:reason]; +} + ++ (NSException *)failureInRaise:(NSString *)expression + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"failure in raise %@ : %@", + expression, reason]; + return [self failureInFile:filename atLine:lineNumber withDescription:reason]; +} + ++ (NSException *)failureInRaise:(NSString *)expression + exception:(NSException *)exception + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... { + va_list vl; + va_start(vl, formatString); + NSString *reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + reason = [NSString stringWithFormat:@"failure in raise %@ (%@) : %@", + expression, exception, reason]; + return [self failureInFile:filename atLine:lineNumber withDescription:reason]; +} + +@end + +@implementation NSObject (GTMSenTestAdditions) +- (void)failWithException:(NSException*)exception { + [exception raise]; +} + +@end + +NSString *STComposeString(NSString *formatString, ...) { + NSString *reason = @""; + if (formatString) { + va_list vl; + va_start(vl, formatString); + reason = [[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease]; + va_end(vl); + } + return reason; +} + +NSString * const SenTestFailureException = @"SenTestFailureException"; + +@implementation SenTestCase +- (void) setUp { +} + +- (void) tearDown { +} +@end + +#endif diff --git a/UnitTesting/GTMUIKit+UnitTesting.h b/UnitTesting/GTMUIKit+UnitTesting.h new file mode 100644 index 0000000..3c231cf --- /dev/null +++ b/UnitTesting/GTMUIKit+UnitTesting.h @@ -0,0 +1,128 @@ +// +// GTMUIKit+UnitTesting.h +// +// Code for making unit testing of graphics/UI easier. Generally you +// will only want to look at the macros: +// GTMAssertDrawingEqualToFile +// GTMAssertViewRepEqualToFile +// and the protocol GTMUnitTestViewDrawer. When using these routines +// make sure you are using device colors and not calibrated/generic colors +// or else your test graphics WILL NOT match across devices/graphics cards. +// +// 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 <UIKit/UIKit.h> +#import "GTMNSObject+UnitTesting.h" + +@protocol GTMUnitTestViewDrawer; + +// Fails when the |a1|'s drawing in an area |a2| does not equal the image file named |a3|. +// See the description of the GTMAssertViewRepEqualToFile macro +// to understand how |a3| is found and written out. +// See the description of the GTMUnitTestView for a better idea +// how the view works. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object that implements the GTMUnitTestViewDrawer protocol +// that is doing the drawing. +// a2: The size of the drawing +// a3: The name of the image file to check against. +// Do not include the extension +// a4: contextInfo to pass to drawer +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// + +#define GTMAssertDrawingEqualToFile(a1, a2, a3, a4, description, ...) \ + do { \ + id<GTMUnitTestViewDrawer> a1Object = (a1); \ + CGSize a2Size = (a2); \ + NSString* a3String = (a3); \ + void *a4ContextInfo = (a4); \ + CGRect frame = CGRectMake(0, 0, a2Size.width, a2Size.height); \ + GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ + GTMAssertObjectImageEqualToImageNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ + } while(0) + +// Category for making unit testing of graphics/UI easier. + +// Allows you to take a state of a view. Supports both image and state. +// See GTMNSObject+UnitTesting.h for details. +@interface UIView (GTMUnitTestingAdditions) <GTMUnitTestingImaging> + +// Returns an image containing a representation suitable for use in comparing against a master image. +// +// Returns: +// an image of the object +- (CGImageRef)gtm_createUnitTestImage; + +// Encodes the state of an object in a manner suitable for comparing against a master state file +// This enables us to determine whether the object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder; + +// Returns whether gtm_unitTestEncodeState should recurse into subviews +// +// Returns: +// should gtm_unitTestEncodeState pick up subview state. +- (BOOL)gtm_shouldEncodeStateForSubviews; +@end + +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol +// This is useful when writing up unit tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, CGSizeMake(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@interface GTMUnitTestView : UIView { + @private + id<GTMUnitTestViewDrawer> drawer_; // delegate for doing drawing (STRONG) + void* contextInfo_; // info passed in by user for them to use when drawing +} + +// Create a GTMUnitTestView. +// +// Args: +// rect: the area to draw. +// drawer: the object that will do the drawing via the GTMUnitTestViewDrawer +// protocol +// contextInfo: +- (id)initWithFrame:(CGRect)frame drawer:(id<GTMUnitTestViewDrawer>)drawer contextInfo:(void*)contextInfo; + +@end + +/// \cond Protocols + +// Formal protocol for doing unit testing of views. See description of +// GTMUnitTestView for details. +@protocol GTMUnitTestViewDrawer <NSObject> + +// Draw the view. Equivalent to drawRect on a standard UIView. +// +// Args: +// rect: the area to draw. +- (void)gtm_unitTestViewDrawRect:(CGRect)rect contextInfo:(void*)contextInfo; + +@end diff --git a/UnitTesting/GTMUIKit+UnitTesting.m b/UnitTesting/GTMUIKit+UnitTesting.m new file mode 100644 index 0000000..7b98886 --- /dev/null +++ b/UnitTesting/GTMUIKit+UnitTesting.m @@ -0,0 +1,118 @@ +// +// GTMUIKit+UnitTesting.m +// +// Category for making unit testing of graphics/UI easier. +// Allows you to save a view out to a image file, and compare a view +// with a previously stored representation to make sure it hasn't changed. +// +// 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 "GTMUIKit+UnitTesting.h" +#import "GTMCALayer+UnitTesting.h" +#import "GTMDefines.h" + +#if !GTM_IPHONE_SDK +#error This file is for iPhone use only +#endif // GTM_IPHONE_SDK + +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol above. This is useful when writing up unit +// tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, CGSizeMake(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@implementation GTMUnitTestView + +- (id)initWithFrame:(CGRect)frame + drawer:(id<GTMUnitTestViewDrawer>)drawer + contextInfo:(void*)contextInfo{ + self = [super initWithFrame:frame]; + if (self != nil) { + drawer_ = [drawer retain]; + contextInfo_ = contextInfo; + } + return self; +} + +- (void)dealloc { + [drawer_ release]; + [super dealloc]; +} + +- (void)drawRect:(CGRect)rect { + [drawer_ gtm_unitTestViewDrawRect:rect contextInfo:contextInfo_]; +} + +@end + +@implementation UIView (GTMUnitTestingAdditions) + +// Returns an image containing a representation of the object +// suitable for use in comparing against a master image. +// NB this means that all colors should be from "NSDevice" color space +// Does all of it's drawing with smoothfonts and antialiasing off +// to avoid issues with font smoothing settings and antialias differences +// between ppc and x86. +// +// Returns: +// an image of the object +- (CGImageRef)gtm_createUnitTestImage { + CALayer* layer = [self layer]; + return [layer gtm_createUnitTestImage]; +} + +// Encodes the state of an object in a manner suitable for comparing +// against a master state file so we can determine whether the +// object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { + [super gtm_unitTestEncodeState:inCoder]; + [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"]; + CALayer* layer = [self layer]; + if (layer) { + [layer gtm_unitTestEncodeState:inCoder]; + } + if ([self gtm_shouldEncodeStateForSubviews]) { + NSEnumerator *subviewEnum = [[self subviews] objectEnumerator]; + UIView *subview = nil; + int i = 0; + while ((subview = [subviewEnum nextObject])) { + [inCoder encodeObject:subview + forKey:[NSString stringWithFormat:@"ViewSubView %d", i]]; + i = i + 1; + } + } +} + +// Returns whether gtm_unitTestEncodeState should recurse into subviews +// +// Returns: +// should gtm_unitTestEncodeState pick up subview state. +- (BOOL)gtm_shouldEncodeStateForSubviews { + return YES; +} + +- (BOOL)gtm_shouldEncodeStateForSublayersOfLayer:(CALayer*)layer { + return NO; +} +@end diff --git a/UnitTesting/GTMUIKit+UnitTestingTest.m b/UnitTesting/GTMUIKit+UnitTestingTest.m new file mode 100644 index 0000000..d39ba9e --- /dev/null +++ b/UnitTesting/GTMUIKit+UnitTestingTest.m @@ -0,0 +1,58 @@ +// +// GTMUIKit+UnitTestingTest.m +// +// 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 <CoreGraphics/CoreGraphics.h> +#import "GTMUIKit+UnitTesting.h" +#import "GTMSenTestCase.h" + +@interface GTMUIView_UnitTestingTest : SenTestCase <GTMUnitTestViewDrawer> +@end + +@implementation GTMUIView_UnitTestingTest + +- (void)testDrawing { + GTMAssertDrawingEqualToFile(self, + CGSizeMake(200,200), + @"GTMUIViewUnitTestingTest", + [UIApplication sharedApplication], + nil); +} + +- (void)testState { + UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)] autorelease]; + UIView *subview = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)] autorelease]; + [view addSubview:subview]; + GTMAssertObjectStateEqualToStateNamed(view, @"GTMUIViewUnitTestingTest", nil); +} + +- (void)gtm_unitTestViewDrawRect:(CGRect)rect contextInfo:(void*)contextInfo { + UIApplication *app = [UIApplication sharedApplication]; + STAssertEqualObjects(app, + contextInfo, + @"Should be a UIApplication"); + CGPoint center = CGPointMake(CGRectGetMidX(rect), + CGRectGetMidY(rect)); + rect = CGRectMake(center.x - 50, center.y - 50, 100, 100); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextAddEllipseInRect(context, rect); + CGContextSetLineWidth(context, 5); + [[UIColor redColor] set]; + CGContextStrokePath(context); +} + +@end diff --git a/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/designable.nib b/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/designable.nib new file mode 100644 index 0000000..e198162 --- /dev/null +++ b/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/designable.nib @@ -0,0 +1,1973 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02"> + <data> + <int key="IBDocument.SystemTarget">0</int> + <string key="IBDocument.SystemVersion">9C31</string> + <string key="IBDocument.InterfaceBuilderVersion">644</string> + <string key="IBDocument.AppKitVersion">949.26</string> + <string key="IBDocument.HIToolboxVersion">352.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="57"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1021"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSCustomObject" id="1014"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1050"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSMenu" id="649796088"> + <string key="NSTitle">AMainMenu</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="694149608"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <object class="NSCustomResource" key="NSOnImage" id="35465992"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuCheckmark</string> + </object> + <object class="NSCustomResource" key="NSMixedImage" id="513340101"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuMixedState</string> + </object> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="110575045"> + <string key="NSTitle">NewApplication</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="238522557"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">About NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="304266470"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="609285721"> + <reference key="NSMenu" ref="110575045"/> + <string type="base64-UTF8" key="NSTitle">UHJlZmVyZW5jZXPigKY</string> + <string key="NSKeyEquiv">,</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="481834944"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1046388886"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Services</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="752062318"> + <string key="NSTitle">Services</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <string key="NSName">_NSServicesMenu</string> + </object> + </object> + <object class="NSMenuItem" id="646227648"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="755159360"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Hide NewApplication</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="342932134"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Hide Others</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1572864</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="908899353"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Show All</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1056857174"> + <reference key="NSMenu" ref="110575045"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="632727374"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Quit NewApplication</string> + <string key="NSKeyEquiv">q</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + <string key="NSName">_NSAppleMenu</string> + </object> + </object> + <object class="NSMenuItem" id="379814623"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">File</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="720053764"> + <string key="NSTitle">File</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="705341025"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">New</string> + <string key="NSKeyEquiv">n</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="722745758"> + <reference key="NSMenu" ref="720053764"/> + <string type="base64-UTF8" key="NSTitle">T3BlbuKApg</string> + <string key="NSKeyEquiv">o</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1025936716"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">Open Recent</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="1065607017"> + <string key="NSTitle">Open Recent</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="759406840"> + <reference key="NSMenu" ref="1065607017"/> + <string key="NSTitle">Clear Menu</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + <string key="NSName">_NSRecentDocumentsMenu</string> + </object> + </object> + <object class="NSMenuItem" id="425164168"> + <reference key="NSMenu" ref="720053764"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="776162233"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">Close</string> + <string key="NSKeyEquiv">w</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1023925487"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">Save</string> + <string key="NSKeyEquiv">s</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="117038363"> + <reference key="NSMenu" ref="720053764"/> + <string type="base64-UTF8" key="NSTitle">U2F2ZSBBc+KApg</string> + <string key="NSKeyEquiv">S</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="579971712"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">Revert to Saved</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1010469920"> + <reference key="NSMenu" ref="720053764"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="294629803"> + <reference key="NSMenu" ref="720053764"/> + <string key="NSTitle">Page Setup...</string> + <string key="NSKeyEquiv">P</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSToolTip"/> + </object> + <object class="NSMenuItem" id="49223823"> + <reference key="NSMenu" ref="720053764"/> + <string type="base64-UTF8" key="NSTitle">UHJpbnTigKY</string> + <string key="NSKeyEquiv">p</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="952259628"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Edit</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="789758025"> + <string key="NSTitle">Edit</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="1058277027"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Undo</string> + <string key="NSKeyEquiv">z</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="790794224"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Redo</string> + <string key="NSKeyEquiv">Z</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1040322652"> + <reference key="NSMenu" ref="789758025"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="296257095"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Cut</string> + <string key="NSKeyEquiv">x</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="860595796"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Copy</string> + <string key="NSKeyEquiv">c</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="29853731"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Paste</string> + <string key="NSKeyEquiv">v</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="437104165"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Delete</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="583158037"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Select All</string> + <string key="NSKeyEquiv">a</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="212016141"> + <reference key="NSMenu" ref="789758025"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="892235320"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Find</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="963351320"> + <string key="NSTitle">Find</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="447796847"> + <reference key="NSMenu" ref="963351320"/> + <string type="base64-UTF8" key="NSTitle">RmluZOKApg</string> + <string key="NSKeyEquiv">f</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">1</int> + </object> + <object class="NSMenuItem" id="326711663"> + <reference key="NSMenu" ref="963351320"/> + <string key="NSTitle">Find Next</string> + <string key="NSKeyEquiv">g</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">2</int> + </object> + <object class="NSMenuItem" id="270902937"> + <reference key="NSMenu" ref="963351320"/> + <string key="NSTitle">Find Previous</string> + <string key="NSKeyEquiv">G</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">3</int> + </object> + <object class="NSMenuItem" id="159080638"> + <reference key="NSMenu" ref="963351320"/> + <string key="NSTitle">Use Selection for Find</string> + <string key="NSKeyEquiv">e</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">7</int> + </object> + <object class="NSMenuItem" id="88285865"> + <reference key="NSMenu" ref="963351320"/> + <string key="NSTitle">Jump to Selection</string> + <string key="NSKeyEquiv">j</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="972420730"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Spelling and Grammar</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="769623530"> + <string key="NSTitle">Spelling and Grammar</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="679648819"> + <reference key="NSMenu" ref="769623530"/> + <string type="base64-UTF8" key="NSTitle">U2hvdyBTcGVsbGluZ+KApg</string> + <string key="NSKeyEquiv">:</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="96193923"> + <reference key="NSMenu" ref="769623530"/> + <string key="NSTitle">Check Spelling</string> + <string key="NSKeyEquiv">;</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="948374510"> + <reference key="NSMenu" ref="769623530"/> + <string key="NSTitle">Check Spelling While Typing</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="967646866"> + <reference key="NSMenu" ref="769623530"/> + <string key="NSTitle">Check Grammar With Spelling</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="507821607"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Substitutions</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="698887838"> + <string key="NSTitle">Substitutions</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="605118523"> + <reference key="NSMenu" ref="698887838"/> + <string key="NSTitle">Smart Copy/Paste</string> + <string key="NSKeyEquiv">f</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">1</int> + </object> + <object class="NSMenuItem" id="197661976"> + <reference key="NSMenu" ref="698887838"/> + <string key="NSTitle">Smart Quotes</string> + <string key="NSKeyEquiv">g</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">2</int> + </object> + <object class="NSMenuItem" id="708854459"> + <reference key="NSMenu" ref="698887838"/> + <string key="NSTitle">Smart Links</string> + <string key="NSKeyEquiv">G</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <int key="NSTag">3</int> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="676164635"> + <reference key="NSMenu" ref="789758025"/> + <string key="NSTitle">Speech</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="785027613"> + <string key="NSTitle">Speech</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="731782645"> + <reference key="NSMenu" ref="785027613"/> + <string key="NSTitle">Start Speaking</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="680220178"> + <reference key="NSMenu" ref="785027613"/> + <string key="NSTitle">Stop Speaking</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="626404410"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Format</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="502084290"> + <string key="NSTitle">Format</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="519768076"> + <reference key="NSMenu" ref="502084290"/> + <string key="NSTitle">Show Fonts</string> + <string key="NSKeyEquiv">t</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="1028416764"> + <reference key="NSMenu" ref="502084290"/> + <string key="NSTitle">Show Colors</string> + <string key="NSKeyEquiv">C</string> + <int key="NSKeyEquivModMask">1179648</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="586577488"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">View</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="466310130"> + <string key="NSTitle">View</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="102151532"> + <reference key="NSMenu" ref="466310130"/> + <string key="NSTitle">Show Toolbar</string> + <string key="NSKeyEquiv">t</string> + <int key="NSKeyEquivModMask">1572864</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + <object class="NSMenuItem" id="237841660"> + <reference key="NSMenu" ref="466310130"/> + <string type="base64-UTF8" key="NSTitle">Q3VzdG9taXplIFRvb2xiYXLigKY</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="391199113"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Help</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="374024848"> + <string key="NSTitle">Help</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="238773614"> + <reference key="NSMenu" ref="374024848"/> + <string key="NSTitle">NewApplication Help</string> + <string key="NSKeyEquiv">?</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="513340101"/> + </object> + </object> + </object> + </object> + </object> + <string key="NSName">_NSMainMenu</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">print:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="49223823"/> + </object> + <int key="connectionID">86</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">runPageLayout:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="294629803"/> + </object> + <int key="connectionID">87</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">clearRecentDocuments:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="759406840"/> + </object> + <int key="connectionID">127</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">orderFrontStandardAboutPanel:</string> + <reference key="source" ref="1021"/> + <reference key="destination" ref="238522557"/> + </object> + <int key="connectionID">142</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performClose:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="776162233"/> + </object> + <int key="connectionID">193</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleContinuousSpellChecking:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="948374510"/> + </object> + <int key="connectionID">222</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">undo:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="1058277027"/> + </object> + <int key="connectionID">223</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">copy:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="860595796"/> + </object> + <int key="connectionID">224</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">checkSpelling:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="96193923"/> + </object> + <int key="connectionID">225</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">paste:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="29853731"/> + </object> + <int key="connectionID">226</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">stopSpeaking:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="680220178"/> + </object> + <int key="connectionID">227</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">cut:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="296257095"/> + </object> + <int key="connectionID">228</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showGuessPanel:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="679648819"/> + </object> + <int key="connectionID">230</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">redo:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="790794224"/> + </object> + <int key="connectionID">231</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">selectAll:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="583158037"/> + </object> + <int key="connectionID">232</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">startSpeaking:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="731782645"/> + </object> + <int key="connectionID">233</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">delete:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="437104165"/> + </object> + <int key="connectionID">235</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performFindPanelAction:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="447796847"/> + </object> + <int key="connectionID">241</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">centerSelectionInVisibleArea:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="88285865"/> + </object> + <int key="connectionID">245</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleGrammarChecking:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="967646866"/> + </object> + <int key="connectionID">347</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleSmartInsertDelete:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="605118523"/> + </object> + <int key="connectionID">355</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticQuoteSubstitution:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="197661976"/> + </object> + <int key="connectionID">356</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticLinkDetection:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="708854459"/> + </object> + <int key="connectionID">357</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showHelp:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="238773614"/> + </object> + <int key="connectionID">360</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">orderFrontColorPanel:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="1028416764"/> + </object> + <int key="connectionID">361</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">saveDocument:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="1023925487"/> + </object> + <int key="connectionID">362</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">saveDocumentAs:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="117038363"/> + </object> + <int key="connectionID">363</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">revertDocumentToSaved:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="579971712"/> + </object> + <int key="connectionID">364</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">runToolbarCustomizationPalette:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="237841660"/> + </object> + <int key="connectionID">365</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleToolbarShown:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="102151532"/> + </object> + <int key="connectionID">366</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hide:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="755159360"/> + </object> + <int key="connectionID">367</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hideOtherApplications:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="342932134"/> + </object> + <int key="connectionID">368</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">terminate:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="632727374"/> + </object> + <int key="connectionID">369</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">unhideAllApplications:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="908899353"/> + </object> + <int key="connectionID">370</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">newDocument:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="705341025"/> + </object> + <int key="connectionID">373</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">openDocument:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="722745758"/> + </object> + <int key="connectionID">374</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1049"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1048"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1021"/> + <reference key="parent" ref="1049"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1014"/> + <reference key="parent" ref="1049"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1050"/> + <reference key="parent" ref="1049"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="649796088"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="694149608"/> + <reference ref="391199113"/> + <reference ref="952259628"/> + <reference ref="379814623"/> + <reference ref="586577488"/> + <reference ref="626404410"/> + </object> + <reference key="parent" ref="1049"/> + <string key="objectName">MainMenu</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="694149608"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="110575045"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">103</int> + <reference key="object" ref="391199113"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="374024848"/> + </object> + <reference key="parent" ref="649796088"/> + <string key="objectName">1</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">217</int> + <reference key="object" ref="952259628"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="789758025"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">83</int> + <reference key="object" ref="379814623"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="720053764"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">81</int> + <reference key="object" ref="720053764"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1023925487"/> + <reference ref="117038363"/> + <reference ref="49223823"/> + <reference ref="722745758"/> + <reference ref="705341025"/> + <reference ref="1025936716"/> + <reference ref="294629803"/> + <reference ref="776162233"/> + <reference ref="425164168"/> + <reference ref="579971712"/> + <reference ref="1010469920"/> + </object> + <reference key="parent" ref="379814623"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">75</int> + <reference key="object" ref="1023925487"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">3</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">80</int> + <reference key="object" ref="117038363"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">8</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">78</int> + <reference key="object" ref="49223823"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">6</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">72</int> + <reference key="object" ref="722745758"/> + <reference key="parent" ref="720053764"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">82</int> + <reference key="object" ref="705341025"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">9</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">124</int> + <reference key="object" ref="1025936716"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1065607017"/> + </object> + <reference key="parent" ref="720053764"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">77</int> + <reference key="object" ref="294629803"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">5</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">73</int> + <reference key="object" ref="776162233"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">1</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">79</int> + <reference key="object" ref="425164168"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">7</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">112</int> + <reference key="object" ref="579971712"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">10</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">74</int> + <reference key="object" ref="1010469920"/> + <reference key="parent" ref="720053764"/> + <string key="objectName">2</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">125</int> + <reference key="object" ref="1065607017"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="759406840"/> + </object> + <reference key="parent" ref="1025936716"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">126</int> + <reference key="object" ref="759406840"/> + <reference key="parent" ref="1065607017"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">205</int> + <reference key="object" ref="789758025"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="437104165"/> + <reference ref="583158037"/> + <reference ref="1058277027"/> + <reference ref="212016141"/> + <reference ref="296257095"/> + <reference ref="29853731"/> + <reference ref="860595796"/> + <reference ref="1040322652"/> + <reference ref="790794224"/> + <reference ref="892235320"/> + <reference ref="972420730"/> + <reference ref="676164635"/> + <reference ref="507821607"/> + </object> + <reference key="parent" ref="952259628"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">202</int> + <reference key="object" ref="437104165"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">198</int> + <reference key="object" ref="583158037"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">207</int> + <reference key="object" ref="1058277027"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">214</int> + <reference key="object" ref="212016141"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">199</int> + <reference key="object" ref="296257095"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">203</int> + <reference key="object" ref="29853731"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">197</int> + <reference key="object" ref="860595796"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">206</int> + <reference key="object" ref="1040322652"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">215</int> + <reference key="object" ref="790794224"/> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">218</int> + <reference key="object" ref="892235320"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="963351320"/> + </object> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">216</int> + <reference key="object" ref="972420730"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="769623530"/> + </object> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">200</int> + <reference key="object" ref="769623530"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="948374510"/> + <reference ref="96193923"/> + <reference ref="679648819"/> + <reference ref="967646866"/> + </object> + <reference key="parent" ref="972420730"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">219</int> + <reference key="object" ref="948374510"/> + <reference key="parent" ref="769623530"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">201</int> + <reference key="object" ref="96193923"/> + <reference key="parent" ref="769623530"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">204</int> + <reference key="object" ref="679648819"/> + <reference key="parent" ref="769623530"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">220</int> + <reference key="object" ref="963351320"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="270902937"/> + <reference ref="88285865"/> + <reference ref="159080638"/> + <reference ref="326711663"/> + <reference ref="447796847"/> + </object> + <reference key="parent" ref="892235320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">213</int> + <reference key="object" ref="270902937"/> + <reference key="parent" ref="963351320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">210</int> + <reference key="object" ref="88285865"/> + <reference key="parent" ref="963351320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">221</int> + <reference key="object" ref="159080638"/> + <reference key="parent" ref="963351320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">208</int> + <reference key="object" ref="326711663"/> + <reference key="parent" ref="963351320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">209</int> + <reference key="object" ref="447796847"/> + <reference key="parent" ref="963351320"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">106</int> + <reference key="object" ref="374024848"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="238773614"/> + </object> + <reference key="parent" ref="391199113"/> + <string key="objectName">2</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">111</int> + <reference key="object" ref="238773614"/> + <reference key="parent" ref="374024848"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="110575045"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="238522557"/> + <reference ref="755159360"/> + <reference ref="908899353"/> + <reference ref="632727374"/> + <reference ref="646227648"/> + <reference ref="609285721"/> + <reference ref="481834944"/> + <reference ref="304266470"/> + <reference ref="1046388886"/> + <reference ref="1056857174"/> + <reference ref="342932134"/> + </object> + <reference key="parent" ref="694149608"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">58</int> + <reference key="object" ref="238522557"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">134</int> + <reference key="object" ref="755159360"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">150</int> + <reference key="object" ref="908899353"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">136</int> + <reference key="object" ref="632727374"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">1111</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">144</int> + <reference key="object" ref="646227648"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">129</int> + <reference key="object" ref="609285721"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">121</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">143</int> + <reference key="object" ref="481834944"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">236</int> + <reference key="object" ref="304266470"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">131</int> + <reference key="object" ref="1046388886"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="752062318"/> + </object> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">149</int> + <reference key="object" ref="1056857174"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">145</int> + <reference key="object" ref="342932134"/> + <reference key="parent" ref="110575045"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">130</int> + <reference key="object" ref="752062318"/> + <reference key="parent" ref="1046388886"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">295</int> + <reference key="object" ref="586577488"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="466310130"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">296</int> + <reference key="object" ref="466310130"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="102151532"/> + <reference ref="237841660"/> + </object> + <reference key="parent" ref="586577488"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">297</int> + <reference key="object" ref="102151532"/> + <reference key="parent" ref="466310130"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">298</int> + <reference key="object" ref="237841660"/> + <reference key="parent" ref="466310130"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">299</int> + <reference key="object" ref="626404410"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="502084290"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">300</int> + <reference key="object" ref="502084290"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="519768076"/> + <reference ref="1028416764"/> + </object> + <reference key="parent" ref="626404410"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">344</int> + <reference key="object" ref="519768076"/> + <reference key="parent" ref="502084290"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">345</int> + <reference key="object" ref="1028416764"/> + <reference key="parent" ref="502084290"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">211</int> + <reference key="object" ref="676164635"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="785027613"/> + </object> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">212</int> + <reference key="object" ref="785027613"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="680220178"/> + <reference ref="731782645"/> + </object> + <reference key="parent" ref="676164635"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">195</int> + <reference key="object" ref="680220178"/> + <reference key="parent" ref="785027613"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">196</int> + <reference key="object" ref="731782645"/> + <reference key="parent" ref="785027613"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">346</int> + <reference key="object" ref="967646866"/> + <reference key="parent" ref="769623530"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">348</int> + <reference key="object" ref="507821607"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="698887838"/> + </object> + <reference key="parent" ref="789758025"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">349</int> + <reference key="object" ref="698887838"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="605118523"/> + <reference ref="197661976"/> + <reference ref="708854459"/> + </object> + <reference key="parent" ref="507821607"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">350</int> + <reference key="object" ref="605118523"/> + <reference key="parent" ref="698887838"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">351</int> + <reference key="object" ref="197661976"/> + <reference key="parent" ref="698887838"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">354</int> + <reference key="object" ref="708854459"/> + <reference key="parent" ref="698887838"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>103.IBPluginDependency</string> + <string>103.ImportedFromIB2</string> + <string>106.IBEditorWindowLastContentRect</string> + <string>106.IBPluginDependency</string> + <string>106.ImportedFromIB2</string> + <string>106.editorWindowContentRectSynchronizationRect</string> + <string>111.IBPluginDependency</string> + <string>111.ImportedFromIB2</string> + <string>112.IBPluginDependency</string> + <string>112.ImportedFromIB2</string> + <string>124.IBPluginDependency</string> + <string>124.ImportedFromIB2</string> + <string>125.IBPluginDependency</string> + <string>125.ImportedFromIB2</string> + <string>125.editorWindowContentRectSynchronizationRect</string> + <string>126.IBPluginDependency</string> + <string>126.ImportedFromIB2</string> + <string>129.IBPluginDependency</string> + <string>129.ImportedFromIB2</string> + <string>130.IBPluginDependency</string> + <string>130.ImportedFromIB2</string> + <string>130.editorWindowContentRectSynchronizationRect</string> + <string>131.IBPluginDependency</string> + <string>131.ImportedFromIB2</string> + <string>134.IBPluginDependency</string> + <string>134.ImportedFromIB2</string> + <string>136.IBPluginDependency</string> + <string>136.ImportedFromIB2</string> + <string>143.IBPluginDependency</string> + <string>143.ImportedFromIB2</string> + <string>144.IBPluginDependency</string> + <string>144.ImportedFromIB2</string> + <string>145.IBPluginDependency</string> + <string>145.ImportedFromIB2</string> + <string>149.IBPluginDependency</string> + <string>149.ImportedFromIB2</string> + <string>150.IBPluginDependency</string> + <string>150.ImportedFromIB2</string> + <string>195.IBPluginDependency</string> + <string>195.ImportedFromIB2</string> + <string>196.IBPluginDependency</string> + <string>196.ImportedFromIB2</string> + <string>197.IBPluginDependency</string> + <string>197.ImportedFromIB2</string> + <string>198.IBPluginDependency</string> + <string>198.ImportedFromIB2</string> + <string>199.IBPluginDependency</string> + <string>199.ImportedFromIB2</string> + <string>200.IBPluginDependency</string> + <string>200.ImportedFromIB2</string> + <string>200.editorWindowContentRectSynchronizationRect</string> + <string>201.IBPluginDependency</string> + <string>201.ImportedFromIB2</string> + <string>202.IBPluginDependency</string> + <string>202.ImportedFromIB2</string> + <string>203.IBPluginDependency</string> + <string>203.ImportedFromIB2</string> + <string>204.IBPluginDependency</string> + <string>204.ImportedFromIB2</string> + <string>205.IBPluginDependency</string> + <string>205.ImportedFromIB2</string> + <string>205.editorWindowContentRectSynchronizationRect</string> + <string>206.IBPluginDependency</string> + <string>206.ImportedFromIB2</string> + <string>207.IBPluginDependency</string> + <string>207.ImportedFromIB2</string> + <string>208.IBPluginDependency</string> + <string>208.ImportedFromIB2</string> + <string>209.IBPluginDependency</string> + <string>209.ImportedFromIB2</string> + <string>210.IBPluginDependency</string> + <string>210.ImportedFromIB2</string> + <string>211.IBPluginDependency</string> + <string>211.ImportedFromIB2</string> + <string>212.IBPluginDependency</string> + <string>212.ImportedFromIB2</string> + <string>212.editorWindowContentRectSynchronizationRect</string> + <string>213.IBPluginDependency</string> + <string>213.ImportedFromIB2</string> + <string>214.IBPluginDependency</string> + <string>214.ImportedFromIB2</string> + <string>215.IBPluginDependency</string> + <string>215.ImportedFromIB2</string> + <string>216.IBPluginDependency</string> + <string>216.ImportedFromIB2</string> + <string>217.IBPluginDependency</string> + <string>217.ImportedFromIB2</string> + <string>218.IBPluginDependency</string> + <string>218.ImportedFromIB2</string> + <string>219.IBPluginDependency</string> + <string>219.ImportedFromIB2</string> + <string>220.IBPluginDependency</string> + <string>220.ImportedFromIB2</string> + <string>220.editorWindowContentRectSynchronizationRect</string> + <string>221.IBPluginDependency</string> + <string>221.ImportedFromIB2</string> + <string>236.IBPluginDependency</string> + <string>236.ImportedFromIB2</string> + <string>29.IBEditorWindowLastContentRect</string> + <string>29.IBPluginDependency</string> + <string>29.ImportedFromIB2</string> + <string>29.WindowOrigin</string> + <string>29.editorWindowContentRectSynchronizationRect</string> + <string>295.IBPluginDependency</string> + <string>296.IBEditorWindowLastContentRect</string> + <string>296.IBPluginDependency</string> + <string>296.editorWindowContentRectSynchronizationRect</string> + <string>297.IBPluginDependency</string> + <string>298.IBPluginDependency</string> + <string>299.IBPluginDependency</string> + <string>300.IBPluginDependency</string> + <string>300.editorWindowContentRectSynchronizationRect</string> + <string>344.IBPluginDependency</string> + <string>345.IBPluginDependency</string> + <string>346.IBPluginDependency</string> + <string>346.ImportedFromIB2</string> + <string>348.IBPluginDependency</string> + <string>348.ImportedFromIB2</string> + <string>349.IBPluginDependency</string> + <string>349.ImportedFromIB2</string> + <string>349.editorWindowContentRectSynchronizationRect</string> + <string>350.IBPluginDependency</string> + <string>350.ImportedFromIB2</string> + <string>351.IBPluginDependency</string> + <string>351.ImportedFromIB2</string> + <string>354.IBPluginDependency</string> + <string>354.ImportedFromIB2</string> + <string>56.IBPluginDependency</string> + <string>56.ImportedFromIB2</string> + <string>57.IBEditorWindowLastContentRect</string> + <string>57.IBPluginDependency</string> + <string>57.ImportedFromIB2</string> + <string>57.editorWindowContentRectSynchronizationRect</string> + <string>58.IBPluginDependency</string> + <string>58.ImportedFromIB2</string> + <string>72.IBPluginDependency</string> + <string>72.ImportedFromIB2</string> + <string>73.IBPluginDependency</string> + <string>73.ImportedFromIB2</string> + <string>74.IBPluginDependency</string> + <string>74.ImportedFromIB2</string> + <string>75.IBPluginDependency</string> + <string>75.ImportedFromIB2</string> + <string>77.IBPluginDependency</string> + <string>77.ImportedFromIB2</string> + <string>78.IBPluginDependency</string> + <string>78.ImportedFromIB2</string> + <string>79.IBPluginDependency</string> + <string>79.ImportedFromIB2</string> + <string>80.IBPluginDependency</string> + <string>80.ImportedFromIB2</string> + <string>81.IBEditorWindowLastContentRect</string> + <string>81.IBPluginDependency</string> + <string>81.ImportedFromIB2</string> + <string>81.editorWindowContentRectSynchronizationRect</string> + <string>82.IBPluginDependency</string> + <string>82.ImportedFromIB2</string> + <string>83.IBPluginDependency</string> + <string>83.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <integer value="1" id="9"/> + <string>{{417, 1069}, {216, 23}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{596, 852}, {216, 23}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{522, 812}, {146, 23}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{436, 809}, {64, 6}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{608, 612}, {275, 83}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{187, 434}, {243, 243}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{608, 612}, {167, 43}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{608, 612}, {241, 103}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{0, 1092}, {407, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{74, 862}</string> + <string>{{6, 978}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{296, 1049}, {234, 43}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{475, 832}, {234, 43}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{231, 634}, {176, 43}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{608, 612}, {215, 63}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{12, 909}, {245, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{23, 794}, {245, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{144, 889}, {199, 203}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{145, 474}, {199, 203}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">374</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSApplication+UnitTesting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMenu</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSMenu+UnitTesting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMenuItem</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSMenuItem+UnitTesting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">AppKit/GTMDelegatingTableColumn.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSObject+BindingUnitTesting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSObject+UnitTesting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMSenTestCase.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">UnitTesting/GTMNSView+UnitTesting.h</string> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../../../GTM.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/keyedobjects.nib b/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 0000000..318a7e9 --- /dev/null +++ b/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/UnitTesting/GTMUIUnitTestingHarness/Info.plist b/UnitTesting/GTMUIUnitTestingHarness/Info.plist new file mode 100644 index 0000000..fe3e712 --- /dev/null +++ b/UnitTesting/GTMUIUnitTestingHarness/Info.plist @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>com.google.GTMUIUnitTestingHarness</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/UnitTesting/GTMUIUnitTestingHarness/main.m b/UnitTesting/GTMUIUnitTestingHarness/main.m new file mode 100644 index 0000000..8fb364d --- /dev/null +++ b/UnitTesting/GTMUIUnitTestingHarness/main.m @@ -0,0 +1,27 @@ +// +// main.m +// GTMUnitTestingTest +// +// 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 <Cocoa/Cocoa.h> +#import "GTMUnitTestingUtilities.h" + +int main(int argc, char *argv[]) { + [GTMUnitTestingUtilities setUpForUIUnitTestsIfBeingTested]; + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/UnitTesting/GTMUIViewUnitTestingTest.gtmUTState b/UnitTesting/GTMUIViewUnitTestingTest.gtmUTState new file mode 100644 index 0000000..87ae9e5 --- /dev/null +++ b/UnitTesting/GTMUIViewUnitTestingTest.gtmUTState @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>$GTMArchive</key> + <string>GTMUnitTestingArchive</string> + <key>$GTMVersion</key> + <integer>1</integer> + <key>LayerIsDoublesided</key> + <true/> + <key>LayerIsHidden</key> + <false/> + <key>LayerIsOpaque</key> + <true/> + <key>LayerOpacity</key> + <real>1</real> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>LayerIsDoublesided</key> + <true/> + <key>LayerIsHidden</key> + <false/> + <key>LayerIsOpaque</key> + <true/> + <key>LayerOpacity</key> + <real>1</real> + <key>ViewIsHidden</key> + <false/> + </dict> +</dict> +</plist> diff --git a/UnitTesting/GTMUIViewUnitTestingTest.png b/UnitTesting/GTMUIViewUnitTestingTest.png Binary files differnew file mode 100644 index 0000000..03fd9f0 --- /dev/null +++ b/UnitTesting/GTMUIViewUnitTestingTest.png diff --git a/UnitTesting/GTMUnitTestingBindingTest.m b/UnitTesting/GTMUnitTestingBindingTest.m new file mode 100644 index 0000000..c0ef93c --- /dev/null +++ b/UnitTesting/GTMUnitTestingBindingTest.m @@ -0,0 +1,117 @@ +// +// GTMUnitTestingBindingTest.m +// +// Copyright 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 <SenTestingKit/SenTestingKit.h> +#import "GTMUnitTestingTest.h" +#import "GTMNSObject+BindingUnitTesting.h" + +@interface GTMUnitTestingBindingTest : SenTestCase { + int expectedFailureCount_; +} +@end + +@interface GTMUnitTestingBindingBadClass : NSObject +@end + +@implementation GTMUnitTestingBindingTest + +// Iterates through all of our subviews testing the exposed bindings +- (void)doSubviewBindingTest:(NSView*)view { + NSArray *subviews = [view subviews]; + NSEnumerator *subviewEnum = [subviews objectEnumerator]; + NSView *subview; + while ((subview = [subviewEnum nextObject])) { + GTMTestExposedBindings(subview, @"testing %@", subview); + [self doSubviewBindingTest:subview]; + } +} + +- (void)testBindings { + // Get our window to work with and test it's bindings + GTMUnitTestingTestController *testWindowController + = [[GTMUnitTestingTestController alloc] initWithWindowNibName:@"GTMUnitTestingTest"]; + NSWindow *window = [testWindowController window]; + GTMTestExposedBindings(window, @"Window failed binding test"); + [self doSubviewBindingTest:[window contentView]]; + [testWindowController release]; + + // Run a test against something with no bindings. + // We're expecting a failure here. + expectedFailureCount_ = 1; + GTMTestExposedBindings(@"foo", @"testing no bindings"); + STAssertEquals(expectedFailureCount_, 0, @"Didn't get expected failures testing bindings"); + + // Run test against some with bad bindings. + // We're expecting failures here. + expectedFailureCount_ = 4; + GTMUnitTestingBindingBadClass *bad = [[[GTMUnitTestingBindingBadClass alloc] init] autorelease]; + GTMTestExposedBindings(bad, @"testing bad bindings"); + STAssertEquals(expectedFailureCount_, 0, @"Didn't get expected failures testing bad bindings"); +} + +- (void)failWithException:(NSException *)anException { + if (expectedFailureCount_ > 0) { + expectedFailureCount_ -= 1; + } else { + [super failWithException:anException]; // COV_NF_LINE - not expecting exception + } +} + +@end + +// Forces several error cases in our binding tests to test them +@implementation GTMUnitTestingBindingBadClass + +NSString *const kGTMKeyWithNoClass = @"keyWithNoClass"; +NSString *const kGTMKeyWithNoValue = @"keyWithNoValue"; +NSString *const kGTMKeyWeCantSet = @"keyWeCantSet"; +NSString *const kGTMKeyThatIsntEqual = @"keyThatIsntEqual"; + +- (NSArray *)exposedBindings { + return [NSArray arrayWithObjects:kGTMKeyWithNoClass, + kGTMKeyWithNoValue, + kGTMKeyWeCantSet, + kGTMKeyThatIsntEqual, + nil]; +} + +- (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding { + + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + [dict setObject:kGTMKeyThatIsntEqual forKey:kGTMKeyThatIsntEqual]; + return dict; +} + +- (Class)valueClassForBinding:(NSString*)binding { + return [binding isEqualTo:kGTMKeyWithNoClass] ? nil : [NSString class]; +} + +- (id)valueForKey:(NSString*)binding { + if ([binding isEqualTo:kGTMKeyWithNoValue]) { + [NSException raise:NSUndefinedKeyException format:nil]; + } + return @"foo"; +} + +- (void)setValue:(id)value forKey:(NSString*)binding { + if ([binding isEqualTo:kGTMKeyWeCantSet]) { + [NSException raise:NSUndefinedKeyException format:nil]; + } +} +@end + diff --git a/UnitTesting/GTMUnitTestingImage.gtmUTState b/UnitTesting/GTMUnitTestingImage.gtmUTState new file mode 100644 index 0000000..969ddf6 --- /dev/null +++ b/UnitTesting/GTMUnitTestingImage.gtmUTState @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>$GTMArchive</key> + <string>GTMUnitTestingArchive</string> + <key>$GTMVersion</key> + <integer>1</integer> + <key>BoolTest</key> + <true/> + <key>BytesTest</key> + <data> + Qnl0ZXNUZXN0 + </data> + <key>DoubleTest</key> + <real>1</real> + <key>FloatTest</key> + <real>1</real> + <key>ImageName</key> + <string>NSApplicationIcon</string> + <key>ImageSize</key> + <string>{128, 128}</string> + <key>Int32Test</key> + <integer>1</integer> + <key>Int64Test</key> + <integer>1</integer> + <key>IntTest</key> + <integer>1</integer> +</dict> +</plist> diff --git a/UnitTesting/GTMUnitTestingImage.tiff b/UnitTesting/GTMUnitTestingImage.tiff Binary files differnew file mode 100644 index 0000000..4d08297 --- /dev/null +++ b/UnitTesting/GTMUnitTestingImage.tiff diff --git a/UnitTesting/GTMUnitTestingTest.h b/UnitTesting/GTMUnitTestingTest.h new file mode 100644 index 0000000..ea9521b --- /dev/null +++ b/UnitTesting/GTMUnitTestingTest.h @@ -0,0 +1,29 @@ +// +// GTMUnitTestingTest.h +// +// Copyright 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 <AppKit/AppKit.h> + +// GTMUnitTestingTestController controller so that initWithWindowNibName can +// find the appropriate bundle to load our nib from. See [GTMUnitTestingTest +// -testUnitTestingFramework] for more info +@interface GTMUnitTestingTestController : NSWindowController { + IBOutlet NSTextField *field_; +} + +- (NSTextField *)textField; +@end diff --git a/UnitTesting/GTMUnitTestingTest.m b/UnitTesting/GTMUnitTestingTest.m new file mode 100644 index 0000000..108bf56 --- /dev/null +++ b/UnitTesting/GTMUnitTestingTest.m @@ -0,0 +1,265 @@ +// +// GTMUnitTestingTest.m +// +// Copyright 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 <SenTestingKit/SenTestingKit.h> +#import "GTMUnitTestingTest.h" +#import "GTMNSApplication+UnitTesting.h" +#import "GTMNSView+UnitTesting.h" + +NSString *const kGTMWindowNibName = @"GTMUnitTestingTest"; +NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; + +@interface GTMUnitTestingTest : SenTestCase { + int expectedFailureCount_; +} +@end + +// GTMUnitTestingTest support classes +@interface GTMUnitTestingView : NSObject <GTMUnitTestViewDrawer> { + BOOL goodContext_; +} +- (BOOL)hadGoodContext; +@end + +@interface GTMUnitTestingDelegate : NSObject { + BOOL didEncode_; +} +- (BOOL)didEncode; +@end + +@interface GTMUnitTestingProxyTest : NSProxy +@end + +@implementation GTMUnitTestingTest + +// Brings up the window defined in the nib and takes a snapshot of it. +// We use the "empty" GTMUnitTestingTestController controller so that +// initWithWindowNibName can find the appropriate bundle to load our nib from. +// For some reason when running unit tests, with all the injecting going on +// the nib loader can get confused as to where it should load a nib from. +// Having a NSWindowController subclass in the same bundle as the nib seems +// to help the nib loader find the nib, and any custom classes that are attached +// to it. +- (void)testUnitTestingFramework { + // set up our delegates so we can test delegate handling + GTMUnitTestingDelegate *appDelegate = [[GTMUnitTestingDelegate alloc] init]; + [NSApp setDelegate:appDelegate]; + + // Get our window + GTMUnitTestingTestController *testWindowController + = [[GTMUnitTestingTestController alloc] initWithWindowNibName:kGTMWindowNibName]; + NSWindow *window = [testWindowController window]; + // Test the app state. This will cover windows and menus + GTMAssertObjectStateEqualToStateNamed(NSApp, + @"GTMUnitTestingTestApp", + @"Testing the app state"); + + // Test the window image and state + GTMAssertObjectEqualToStateAndImageNamed(window, + kGTMWindowSaveFileName, + @"Testing the window image and state"); + + // Verify that all of our delegate encoders got called + STAssertTrue([appDelegate didEncode], @"app delegate didn't get called?"); + + // Clean up + [NSApp setDelegate:nil]; + [appDelegate release]; + [window close]; +} + +- (void)testViewUnitTesting { + GTMUnitTestingView *unitTestingView = [[GTMUnitTestingView alloc] init]; + GTMAssertDrawingEqualToFile(unitTestingView, + NSMakeSize(200,200), + @"GTMUnitTestingView", + NSApp, + @"Testing view drawing"); + STAssertTrue([unitTestingView hadGoodContext], @"bad context?"); + [unitTestingView release]; +} + +- (void)testImageUnitTesting { + NSImage *image = [NSImage imageNamed:@"NSApplicationIcon"]; + GTMUnitTestingDelegate *imgDelegate = [[GTMUnitTestingDelegate alloc] init]; + [image setDelegate:imgDelegate]; + GTMAssertObjectEqualToStateAndImageNamed(image, + @"GTMUnitTestingImage", + @"Testing NSImage image and state"); + STAssertTrue([imgDelegate didEncode], @"imgDelegate didn't get called?"); + [image setDelegate:nil]; + [imgDelegate release]; +} + +- (void)testFailures { + NSString *const bogusTestName = @"GTMUnitTestTestingFailTest"; + NSString *tempDir = NSTemporaryDirectory(); + STAssertNotNil(tempDir, @"No Temp Dir?"); + NSString *originalPath = [NSObject gtm_getUnitTestSaveToDirectory]; + STAssertNotNil(originalPath, @"No save dir?"); + [NSObject gtm_setUnitTestSaveToDirectory:tempDir]; + STAssertEqualObjects(tempDir, [NSObject gtm_getUnitTestSaveToDirectory], + @"Save to dir not set?"); + NSString *statePath = [self gtm_saveToPathForStateNamed:bogusTestName]; + STAssertNotNil(statePath, @"no state path?"); + NSString *imagePath = [self gtm_saveToPathForImageNamed:bogusTestName]; + STAssertNotNil(imagePath, @"no image path?"); + GTMUnitTestingTestController *testWindowController + = [[GTMUnitTestingTestController alloc] initWithWindowNibName:kGTMWindowNibName]; + NSWindow *window = [testWindowController window]; + + // Test against a golden master filename that doesn't exist + expectedFailureCount_ = 2; + GTMAssertObjectEqualToStateAndImageNamed(window, + bogusTestName, + @"Creating image and state files"); + STAssertEquals(expectedFailureCount_, 0, + @"Didn't get expected failures creating files"); + + // Change our image and state and verify failures + [[testWindowController textField] setStringValue:@"Foo"]; + expectedFailureCount_ = 2; + GTMAssertObjectEqualToStateAndImageNamed(window, + kGTMWindowSaveFileName, + @"Testing the window image and state"); + STAssertEquals(expectedFailureCount_, 0, + @"Didn't get expected failures testing files"); + + // Now change the size of our image and verify failures + NSRect oldFrame = [window frame]; + NSRect newFrame = oldFrame; + newFrame.size.width += 1; + [window setFrame:newFrame display:YES]; + expectedFailureCount_ = 1; + GTMAssertObjectImageEqualToImageNamed(window, + kGTMWindowSaveFileName, + @"Testing the changed window size"); + [window setFrame:oldFrame display:YES]; + + // Set our unit test save dir to a bogus directory and + // run the tests again. + [NSObject gtm_setUnitTestSaveToDirectory:@"/zim/blatz/foo/bob/bar"]; + expectedFailureCount_ = 2; + GTMAssertObjectEqualToStateAndImageNamed(window, + kGTMWindowSaveFileName, + @"Testing the window image and state"); + STAssertEquals(expectedFailureCount_, 0, + @"Didn't get expected failures testing files"); + expectedFailureCount_ = 2; + GTMAssertObjectEqualToStateAndImageNamed(window, + @"GTMUnitTestingWindowDoesntExist", + @"Testing the window image and state"); + STAssertEquals(expectedFailureCount_, 0, + @"Didn't get expected failures testing files"); + + // Reset our unit test save dir + [NSObject gtm_setUnitTestSaveToDirectory:nil]; + + // Test against something that doesn't have an image + expectedFailureCount_ = 1; + GTMAssertObjectImageEqualToImageNamed(@"a string", + @"GTMStringsDontHaveImages", + @"Testing that strings should fail"); + STAssertEquals(expectedFailureCount_, 0, @"Didn't get expected failures testing files"); + + // Test against something that doesn't implement our support + expectedFailureCount_ = 1; + GTMUnitTestingProxyTest *proxy = [[GTMUnitTestingProxyTest alloc] init]; + GTMAssertObjectStateEqualToStateNamed(proxy, + @"NSProxiesDontDoState", + @"Testing that NSProxy should fail"); + STAssertEquals(expectedFailureCount_, 0, @"Didn't get expected failures testing proxy"); + [proxy release]; + + [window close]; +} + +- (void)failWithException:(NSException *)anException { + if (expectedFailureCount_ > 0) { + expectedFailureCount_ -= 1; + } else { + [super failWithException:anException]; // COV_NF_LINE - not expecting exception + } +} + + +@end + +@implementation GTMUnitTestingTestController +- (NSTextField *)textField { + return field_; +} + +- (void)dealloc { + NSWindow *window = [self window]; + int count = [window retainCount]; + + // Spinning the run loop here to get rid of the window. Stupid issue + // where there's a delayed selector holding a retain count on our window + // rdar://5851458 - Closing a window with a NSTextView in it should get rid of it immediately + while (count == [window retainCount]) { + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } + [super dealloc]; +} +@end + +@implementation GTMUnitTestingDelegate + +- (void)gtm_unitTestEncoderWillEncode:(id)sender inCoder:(NSCoder*)inCoder { + // Test various encodings + [inCoder encodeBool:YES forKey:@"BoolTest"]; + [inCoder encodeInt:1 forKey:@"IntTest"]; + [inCoder encodeInt32:1 forKey:@"Int32Test"]; + [inCoder encodeInt64:1 forKey:@"Int64Test"]; + [inCoder encodeFloat:1.0f forKey:@"FloatTest"]; + [inCoder encodeDouble:1.0 forKey:@"DoubleTest"]; + [inCoder encodeBytes:(uint8_t*)"BytesTest" length:9 forKey:@"BytesTest"]; + didEncode_ = YES; +} + +- (BOOL)didEncode { + return didEncode_; +} +@end + +@implementation GTMUnitTestingView + +- (void)gtm_unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo { + [[NSColor redColor] set]; + NSRectFill(rect); + goodContext_ = [(id)contextInfo isEqualTo:NSApp]; +} + +- (BOOL)hadGoodContext { + return goodContext_; +} +@end + +// GTMUnitTestingProxyTest is for testing the case where we don't conform to +// the GTMUnitTestingEncoding protocol. +@implementation GTMUnitTestingProxyTest +- (id)init { + return self; +} + +- (BOOL)conformsToProtocol:(Protocol *)protocol { + return NO; +} + +@end diff --git a/UnitTesting/GTMUnitTestingTest.nib/classes.nib b/UnitTesting/GTMUnitTestingTest.nib/classes.nib new file mode 100644 index 0000000..cc51f67 --- /dev/null +++ b/UnitTesting/GTMUnitTestingTest.nib/classes.nib @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBClasses</key> + <array> + <dict> + <key>CLASS</key> + <string>NSView</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSApplication</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSTextField</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSControl</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSMenu</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSObject</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSControl</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSView</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSObject</string> + <key>LANGUAGE</key> + <string>ObjC</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSCell</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSObject</string> + </dict> + <dict> + <key>CLASS</key> + <string>NSWindow</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>GTMUnitTestingTestController</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>OUTLETS</key> + <dict> + <key>field_</key> + <string>NSTextField</string> + </dict> + <key>SUPERCLASS</key> + <string>NSWindowController</string> + </dict> + </array> + <key>IBVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/UnitTesting/GTMUnitTestingTest.nib/info.nib b/UnitTesting/GTMUnitTestingTest.nib/info.nib new file mode 100644 index 0000000..3fb1618 --- /dev/null +++ b/UnitTesting/GTMUnitTestingTest.nib/info.nib @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBFramework Version</key> + <string>644</string> + <key>IBLastKnownRelativeProjectPath</key> + <string>../GTM.xcodeproj</string> + <key>IBOldestOS</key> + <integer>5</integer> + <key>IBOpenObjects</key> + <array> + <integer>2</integer> + </array> + <key>IBSystem Version</key> + <string>9C31</string> + <key>targetFramework</key> + <string>IBCocoaFramework</string> +</dict> +</plist> diff --git a/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib b/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib Binary files differnew file mode 100644 index 0000000..d3104e2 --- /dev/null +++ b/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib diff --git a/UnitTesting/GTMUnitTestingTestApp.gtmUTState b/UnitTesting/GTMUnitTestingTestApp.gtmUTState new file mode 100644 index 0000000..1b7c4e5 --- /dev/null +++ b/UnitTesting/GTMUnitTestingTestApp.gtmUTState @@ -0,0 +1,1879 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>$GTMArchive</key> + <string>GTMUnitTestingArchive</string> + <key>$GTMVersion</key> + <integer>1</integer> + <key>ApplicationMainWindow</key> + <integer>0</integer> + <key>BoolTest</key> + <true/> + <key>BytesTest</key> + <data> + Qnl0ZXNUZXN0 + </data> + <key>DoubleTest</key> + <real>1</real> + <key>FloatTest</key> + <real>1</real> + <key>Int32Test</key> + <integer>1</integer> + <key>Int64Test</key> + <integer>1</integer> + <key>IntTest</key> + <integer>1</integer> + <key>MenuBar</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>About NewApplication</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 10</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>q</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Quit NewApplication</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>,</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Preferences…</string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 4</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuTitle</key> + <string>Services</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Services</string> + </dict> + <key>MenuItem 5</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 6</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>h</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Hide NewApplication</string> + </dict> + <key>MenuItem 7</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>h</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Hide Others</string> + </dict> + <key>MenuItem 8</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Show All</string> + </dict> + <key>MenuItem 9</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuTitle</key> + <string>Apple</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>n</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>New</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>o</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Open…</string> + </dict> + <key>MenuItem 10</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>P</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Page Setup...</string> + <key>MenuItemTooltip</key> + <string></string> + </dict> + <key>MenuItem 11</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>p</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Print…</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Clear Menu</string> + </dict> + <key>MenuTitle</key> + <string>Open Recent</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Open Recent</string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 4</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>w</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Close</string> + </dict> + <key>MenuItem 5</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <true/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>w</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Close All</string> + </dict> + <key>MenuItem 6</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>s</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Save</string> + </dict> + <key>MenuItem 7</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>S</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Save As…</string> + </dict> + <key>MenuItem 8</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Revert to Saved</string> + </dict> + <key>MenuItem 9</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuTitle</key> + <string>File</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>File</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>z</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Undo</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>Z</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Redo</string> + </dict> + <key>MenuItem 10</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>:</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Show Spelling…</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>;</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Check Spelling</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Check Spelling While Typing</string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Check Grammar With Spelling</string> + </dict> + <key>MenuTitle</key> + <string>Spelling and Grammar</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Spelling and Grammar</string> + </dict> + <key>MenuItem 11</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>1</integer> + <key>MenuItemTitle</key> + <string>Smart Copy/Paste</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>2</integer> + <key>MenuItemTitle</key> + <string>Smart Quotes</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>3</integer> + <key>MenuItemTitle</key> + <string>Smart Links</string> + </dict> + <key>MenuTitle</key> + <string>Substitutions</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Substitutions</string> + </dict> + <key>MenuItem 12</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Start Speaking</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Stop Speaking</string> + </dict> + <key>MenuTitle</key> + <string>Speech</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Speech</string> + </dict> + <key>MenuItem 13</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 14</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Special Characters…</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>x</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Cut</string> + </dict> + <key>MenuItem 4</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>c</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Copy</string> + </dict> + <key>MenuItem 5</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>v</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Paste</string> + </dict> + <key>MenuItem 6</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Delete</string> + </dict> + <key>MenuItem 7</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>a</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Select All</string> + </dict> + <key>MenuItem 8</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <false/> + <key>MenuItemIsSeparator</key> + <true/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string></string> + </dict> + <key>MenuItem 9</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>f</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>1</integer> + <key>MenuItemTitle</key> + <string>Find…</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>g</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>2</integer> + <key>MenuItemTitle</key> + <string>Find Next</string> + </dict> + <key>MenuItem 2</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>G</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>3</integer> + <key>MenuItemTitle</key> + <string>Find Previous</string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>e</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>7</integer> + <key>MenuItemTitle</key> + <string>Use Selection for Find</string> + </dict> + <key>MenuItem 4</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>j</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Jump to Selection</string> + </dict> + <key>MenuTitle</key> + <string>Find</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Find</string> + </dict> + <key>MenuTitle</key> + <string>Edit</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Edit</string> + </dict> + <key>MenuItem 3</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>t</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Show Fonts</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>C</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Show Colors</string> + </dict> + <key>MenuTitle</key> + <string>Format</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Format</string> + </dict> + <key>MenuItem 4</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>t</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Show Toolbar</string> + </dict> + <key>MenuItem 1</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Customize Toolbar…</string> + </dict> + <key>MenuTitle</key> + <string>View</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>View</string> + </dict> + <key>MenuItem 5</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string></string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemSubmenu</key> + <dict> + <key>MenuItem 0</key> + <dict> + <key>MenuItemIndentationLevel</key> + <integer>0</integer> + <key>MenuItemIsAlternate</key> + <false/> + <key>MenuItemIsEnabled</key> + <true/> + <key>MenuItemIsSeparator</key> + <false/> + <key>MenuItemKeyEquivalent</key> + <string>?</string> + <key>MenuItemState</key> + <integer>0</integer> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>NewApplication Help</string> + </dict> + <key>MenuTitle</key> + <string>Help</string> + </dict> + <key>MenuItemTag</key> + <integer>0</integer> + <key>MenuItemTitle</key> + <string>Help</string> + </dict> + <key>MenuTitle</key> + <string>AMainMenu</string> + </dict> + <key>Window 0</key> + <dict> + <key>WindowContent</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTableView</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 10</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 11</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>HaHa</string> + <key>CellValue</key> + <string>HaHa</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>HaHa</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>CellValue</key> + <string>Still Haven't Found What I'm Searching For</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSearchField</string> + <key>ControlValue</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Foo</string> + <key>CellValue</key> + <string>Foo</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>Foo</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 5</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>CheckMate!</string> + <key>CellValue</key> + <string>1</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>1</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 6</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string></string> + <key>CellValue</key> + <string>50</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSlider</string> + <key>ControlValue</key> + <string>50</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 7</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Cancel</string> + <key>CellValue</key> + <string>0</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>0</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 8</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSColorWell</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 9</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>WindowIsMain</key> + <false/> + <key>WindowIsVisible</key> + <false/> + <key>WindowTitle</key> + <string>Window</string> + </dict> + <key>Window 1</key> + <dict> + <key>WindowContent</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTableView</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 10</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 11</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>HaHa</string> + <key>CellValue</key> + <string>HaHa</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>HaHa</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>CellValue</key> + <string>Still Haven't Found What I'm Searching For</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSearchField</string> + <key>ControlValue</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Once upon a time</string> + <key>CellValue</key> + <string>Once upon a time</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>Once upon a time</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 5</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>CheckMate!</string> + <key>CellValue</key> + <string>1</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>1</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 6</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string></string> + <key>CellValue</key> + <string>50</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSlider</string> + <key>ControlValue</key> + <string>50</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 7</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Cancel</string> + <key>CellValue</key> + <string>0</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>0</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 8</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSColorWell</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 9</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>WindowIsMain</key> + <false/> + <key>WindowIsVisible</key> + <true/> + <key>WindowTitle</key> + <string>Window</string> + </dict> +</dict> +</plist> diff --git a/UnitTesting/GTMUnitTestingUtilities.h b/UnitTesting/GTMUnitTestingUtilities.h new file mode 100644 index 0000000..17ec766 --- /dev/null +++ b/UnitTesting/GTMUnitTestingUtilities.h @@ -0,0 +1,42 @@ +// +// GTMUnitTestingUtilities.h +// +// 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 <objc/objc.h> + +// Collection of utilities for unit testing +@interface GTMUnitTestingUtilities : NSObject + +// Returns YES if we are currently being unittested. ++ (BOOL)areWeBeingUnitTested; + +// Sets up the user interface so that we can run consistent UI unittests on +// it. This includes setting scroll bar types, setting selection colors +// setting color spaces etc so that everything is consistent across machines. +// This should be called in main, before NSApplicationMain is called. ++ (void)setUpForUIUnitTests; + +// Syntactic sugar combining the above, and wrapping them in an +// NSAutoreleasePool so that your main can look like this: +// int main(int argc, const char *argv[]) { +// [UnitTestingUtilities setUpForUIUnitTestsIfBeingTested]; +// return NSApplicationMain(argc, argv); +// } ++ (void)setUpForUIUnitTestsIfBeingTested; + +@end + diff --git a/UnitTesting/GTMUnitTestingUtilities.m b/UnitTesting/GTMUnitTestingUtilities.m new file mode 100644 index 0000000..7c1b915 --- /dev/null +++ b/UnitTesting/GTMUnitTestingUtilities.m @@ -0,0 +1,172 @@ +// +// GTMUnitTestingUtilities.m +// +// 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 "GTMUnitTestingUtilities.h" +#import <AppKit/AppKit.h> +#import "GTMDefines.h" + +// The Users profile before we change it on them +static CMProfileRef gCurrentColorProfile = NULL; + +// Compares two color profiles +static BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b); +// Stores the user's color profile away, and changes over to generic. +static void SetColorProfileToGenericRGB(); +// Restores the users profile. +static void RestoreColorProfile(void); + +@implementation GTMUnitTestingUtilities + +// Returns YES if we are currently being unittested. ++ (BOOL)areWeBeingUnitTested { + BOOL answer = NO; + + // Check to see if the SenTestProbe class is linked in before we call it. + Class SenTestProbeClass = NSClassFromString(@"SenTestProbe"); + if (SenTestProbeClass != Nil) { + // Doing this little dance so we don't actually have to link + // SenTestingKit in + SEL selector = NSSelectorFromString(@"isTesting"); + NSMethodSignature *sig = [SenTestProbeClass methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:selector]; + [invocation invokeWithTarget:SenTestProbeClass]; + [invocation getReturnValue:&answer]; + } + return answer; +} + +// Sets up the user interface so that we can run consistent UI unittests on it. ++ (void)setUpForUIUnitTests { + // Give some names to undocumented defaults values + static const int MediumFontSmoothing = 2; + static const int BlueTintedAppearance = 1; + + // This sets up some basic values that we want as our defaults for doing pixel + // based user interface tests. These defaults only apply to the unit test app, + // except or the color profile which will be set system wide, and then + // restored when the tests complete. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + // Scroll arrows together bottom + [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"]; + // Smallest font size to CG should perform antialiasing on + [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; + // Type of smoothing + [defaults setInteger:MediumFontSmoothing forKey:@"AppleFontSmoothing"]; + // Blue aqua + [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"]; + // Standard highlight colors + [defaults setObject:@"0.709800 0.835300 1.000000" + forKey:@"AppleHighlightColor"]; + [defaults setObject:@"0.500000 0.500000 0.500000" + forKey:@"AppleOtherHighlightColor"]; + // Use english plz + [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"]; + // How fast should we draw sheets. This speeds up the sheet tests considerably + [defaults setFloat:.001 forKey:@"NSWindowResizeTime"]; + // Switch over the screen profile to "generic rgb". This installs an + // atexit handler to return our profile back when we are done. + SetColorProfileToGenericRGB(); +} + ++ (void)setUpForUIUnitTestsIfBeingTested { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if ([self areWeBeingUnitTested]) { + [self setUpForUIUnitTests]; + } + [pool release]; +} +@end + + +BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b) { + BOOL equal = YES; + if (a != b) { + CMProfileMD5 aMD5; + CMProfileMD5 bMD5; + CMError aMD5Err = CMGetProfileMD5(a, aMD5); + CMError bMD5Err = CMGetProfileMD5(b, bMD5); + equal = (!aMD5Err && + !bMD5Err && + !memcmp(aMD5, bMD5, sizeof(CMProfileMD5))) ? YES : NO; + } + return equal; +} + +static void RestoreColorProfile(void) { + if (gCurrentColorProfile) { + CGDirectDisplayID displayID = CGMainDisplayID(); + int error = CMSetProfileByAVID((UInt32)displayID, gCurrentColorProfile); + if (error) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to restore previous color profile! " + "You may need to open System Preferences : Displays : Color " + "and manually restore your color settings. (Error: %i)", error); + // COV_NF_END + } else { + _GTMDevLog(@"Color profile restored"); + } + gCurrentColorProfile = NULL; + } +} + +void SetColorProfileToGenericRGB() { + NSColorSpace *genericSpace = [NSColorSpace genericRGBColorSpace]; + CMProfileRef genericProfile = (CMProfileRef)[genericSpace colorSyncProfile]; + CMProfileRef previousProfile; + CGDirectDisplayID displayID = CGMainDisplayID(); + CMError error = CMGetProfileByAVID((UInt32)displayID, &previousProfile); + if (error) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to get current color profile. " + "I will not be able to restore your current profile, thus I'm " + "not changing it. Many unit tests may fail as a result. (Error: %i)", + error); + return; + // COV_NF_END + } + if (AreCMProfilesEqual(genericProfile, previousProfile)) { + return; + } + CFStringRef previousProfileName; + CFStringRef genericProfileName; + CMCopyProfileDescriptionString(previousProfile, &previousProfileName); + CMCopyProfileDescriptionString(genericProfile, &genericProfileName); + + _GTMDevLog(@"Temporarily changing your system color profile from \"%@\" to \"%@\".", + previousProfileName, genericProfileName); + _GTMDevLog(@"This allows the pixel-based unit-tests to have consistent color " + "values across all machines."); + _GTMDevLog(@"The colors on your screen will change for the duration of the testing."); + + + if ((error = CMSetProfileByAVID((UInt32)displayID, genericProfile))) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to set color profile to \"%@\"! Many unit tests will fail as " + "a result. (Error: %i)", genericProfileName, error); + // COV_NF_END + } else { + gCurrentColorProfile = previousProfile; + atexit(RestoreColorProfile); + } + CFRelease(previousProfileName); + CFRelease(genericProfileName); +} diff --git a/UnitTesting/GTMUnitTestingView.tiff b/UnitTesting/GTMUnitTestingView.tiff Binary files differnew file mode 100644 index 0000000..228df73 --- /dev/null +++ b/UnitTesting/GTMUnitTestingView.tiff diff --git a/UnitTesting/GTMUnitTestingWindow.gtmUTState b/UnitTesting/GTMUnitTestingWindow.gtmUTState new file mode 100644 index 0000000..27dd08e --- /dev/null +++ b/UnitTesting/GTMUnitTestingWindow.gtmUTState @@ -0,0 +1,291 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>$GTMArchive</key> + <string>GTMUnitTestingArchive</string> + <key>$GTMVersion</key> + <integer>1</integer> + <key>WindowContent</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTableView</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 10</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 1</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <false/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSScroller</string> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 11</key> + <dict> + <key>ViewIsHidden</key> + <false/> + <key>ViewSubView 0</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>ViewSubView 2</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>HaHa</string> + <key>CellValue</key> + <string>HaHa</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>HaHa</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 3</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>CellValue</key> + <string>Still Haven't Found What I'm Searching For</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSearchField</string> + <key>ControlValue</key> + <string>Still Haven't Found What I'm Searching For</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 4</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Once upon a time</string> + <key>CellValue</key> + <string>Once upon a time</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSTextField</string> + <key>ControlValue</key> + <string>Once upon a time</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 5</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>CheckMate!</string> + <key>CellValue</key> + <string>1</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>1</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 6</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>1</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string></string> + <key>CellValue</key> + <string>50</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSSlider</string> + <key>ControlValue</key> + <string>50</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 7</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlSelectedCell</key> + <dict> + <key>CellState</key> + <integer>0</integer> + <key>CellTag</key> + <integer>0</integer> + <key>CellTitle</key> + <string>Cancel</string> + <key>CellValue</key> + <string>0</string> + </dict> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSButton</string> + <key>ControlValue</key> + <string>0</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 8</key> + <dict> + <key>ControlIsEnabled</key> + <true/> + <key>ControlTag</key> + <integer>0</integer> + <key>ControlType</key> + <string>NSColorWell</string> + <key>ViewIsHidden</key> + <false/> + </dict> + <key>ViewSubView 9</key> + <dict> + <key>ViewIsHidden</key> + <false/> + </dict> + </dict> + <key>WindowIsMain</key> + <false/> + <key>WindowIsVisible</key> + <true/> + <key>WindowTitle</key> + <string>Window</string> +</dict> +</plist> diff --git a/UnitTesting/GTMUnitTestingWindow.tiff b/UnitTesting/GTMUnitTestingWindow.tiff Binary files differnew file mode 100644 index 0000000..63f5649 --- /dev/null +++ b/UnitTesting/GTMUnitTestingWindow.tiff diff --git a/UnitTesting/RunIPhoneUnitTest.sh b/UnitTesting/RunIPhoneUnitTest.sh new file mode 100755 index 0000000..50709d3 --- /dev/null +++ b/UnitTesting/RunIPhoneUnitTest.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# RunIPhoneUnitTest.sh +# Copyright 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. +# +# Runs all unittests through the iPhone simulator + +export DYLD_ROOT_PATH="$SDKROOT" +export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR" +export IPHONE_SIMULATOR_ROOT="$SDKROOT" +export CFFIXED_USER_HOME="$USER_LIBRARY_DIR/Application Support/iPhone Simulator/User" +"$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents +exit 0 diff --git a/UnitTesting/RunMacOSUnitTests.sh b/UnitTesting/RunMacOSUnitTests.sh new file mode 100755 index 0000000..2c8440a --- /dev/null +++ b/UnitTesting/RunMacOSUnitTests.sh @@ -0,0 +1,41 @@ +# +# RunUnitTests.sh +# Copyright 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. +# +# Run the unit tests in this test bundle. +# Set up some env variables to make things as likely to crash as possible. +# See http://developer.apple.com/technotes/tn2004/tn2124.html for details. +# + +export MallocScribble=YES +export MallocPreScribble=YES +export MallocGuardEdges=YES +# CFZombieLevel disabled because it doesn't play well with the +# security framework +# export CFZombieLevel=3 +export NSAutoreleaseFreedObjectCheckEnabled=YES +export NSZombieEnabled=YES +export OBJC_DEBUG_FRAGILE_SUPERCLASSES=YES + +# If we have debug libraries on the machine, we'll use them +# unless a target has specifically turned them off +if [ ! $GTM_NO_DEBUG_FRAMEWORKS ]; then + if [ -f "/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation_debug" ]; then + echo ---- Using _debug frameworks ---- + export DYLD_IMAGE_SUFFIX=_debug + fi +fi + +"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests" |