diff options
Diffstat (limited to 'UnitTesting/GTMAppKit+UnitTesting.m')
-rwxr-xr-x | UnitTesting/GTMAppKit+UnitTesting.m | 304 |
1 files changed, 304 insertions, 0 deletions
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 + |