aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-04-14 17:21:02 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-04-14 17:21:02 +0000
commitcdf070c8d76ffc4eaa24e8671756cbbe9ceb2890 (patch)
treefaa9ae3a72a6591d6a6add7ceed7f91e92ade11f /UnitTesting
parent0aaecac6ff2bc89e58a0c8c6d6ad62e02fb2b011 (diff)
See the ReleaseNotes for the full details, highlights:
- bug fixes - code coverage support - more complete unittests - full support for unittesting UIs - support for the iphone sdk (include ui unittesting)
Diffstat (limited to 'UnitTesting')
-rwxr-xr-x[-rw-r--r--]UnitTesting/GTMAppKit+UnitTesting.h (renamed from UnitTesting/GTMNSView+UnitTesting.h)83
-rwxr-xr-xUnitTesting/GTMAppKit+UnitTesting.m304
-rw-r--r--UnitTesting/GTMCALayer+UnitTesting.h46
-rw-r--r--UnitTesting/GTMCALayer+UnitTesting.m89
-rwxr-xr-xUnitTesting/GTMIPhoneUnitTestMain.m181
-rw-r--r--UnitTesting/GTMNSObject+BindingUnitTesting.h103
-rw-r--r--UnitTesting/GTMNSObject+BindingUnitTesting.m440
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.h513
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.m886
-rw-r--r--UnitTesting/GTMNSView+UnitTesting.m135
-rw-r--r--UnitTesting/GTMSenTestCase.h803
-rw-r--r--UnitTesting/GTMSenTestCase.m140
-rw-r--r--UnitTesting/GTMUIKit+UnitTesting.h128
-rw-r--r--UnitTesting/GTMUIKit+UnitTesting.m118
-rw-r--r--UnitTesting/GTMUIKit+UnitTestingTest.m58
-rw-r--r--UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/designable.nib1973
-rw-r--r--UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 16183 bytes
-rw-r--r--UnitTesting/GTMUIUnitTestingHarness/Info.plist28
-rw-r--r--UnitTesting/GTMUIUnitTestingHarness/main.m27
-rw-r--r--UnitTesting/GTMUIViewUnitTestingTest.gtmUTState33
-rw-r--r--UnitTesting/GTMUIViewUnitTestingTest.pngbin0 -> 3214 bytes
-rw-r--r--UnitTesting/GTMUnitTestingBindingTest.m117
-rw-r--r--UnitTesting/GTMUnitTestingImage.gtmUTState30
-rw-r--r--UnitTesting/GTMUnitTestingImage.tiffbin0 -> 67190 bytes
-rw-r--r--UnitTesting/GTMUnitTestingTest.h29
-rw-r--r--UnitTesting/GTMUnitTestingTest.m265
-rw-r--r--UnitTesting/GTMUnitTestingTest.nib/classes.nib86
-rw-r--r--UnitTesting/GTMUnitTestingTest.nib/info.nib20
-rw-r--r--UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nibbin0 -> 15667 bytes
-rw-r--r--UnitTesting/GTMUnitTestingTestApp.gtmUTState1879
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.h42
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.m172
-rw-r--r--UnitTesting/GTMUnitTestingView.tiffbin0 -> 161670 bytes
-rw-r--r--UnitTesting/GTMUnitTestingWindow.gtmUTState291
-rw-r--r--UnitTesting/GTMUnitTestingWindow.tiffbin0 -> 21226 bytes
-rwxr-xr-xUnitTesting/RunIPhoneUnitTest.sh24
-rwxr-xr-xUnitTesting/RunMacOSUnitTests.sh41
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
new file mode 100644
index 0000000..318a7e9
--- /dev/null
+++ b/UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
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
new file mode 100644
index 0000000..03fd9f0
--- /dev/null
+++ b/UnitTesting/GTMUIViewUnitTestingTest.png
Binary files differ
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
new file mode 100644
index 0000000..4d08297
--- /dev/null
+++ b/UnitTesting/GTMUnitTestingImage.tiff
Binary files differ
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
new file mode 100644
index 0000000..d3104e2
--- /dev/null
+++ b/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib
Binary files differ
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
new file mode 100644
index 0000000..228df73
--- /dev/null
+++ b/UnitTesting/GTMUnitTestingView.tiff
Binary files differ
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
new file mode 100644
index 0000000..63f5649
--- /dev/null
+++ b/UnitTesting/GTMUnitTestingWindow.tiff
Binary files differ
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"