1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h" 6 7 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 8 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h" 9 #import "testing/gtest_mac.h" 10 11 namespace { 12 13 const int kSystemSheetReturnCode = 77; 14 15 } // namespace 16 17 @interface ConstrainedWindowSystemSheetTest 18 : NSObject <ConstrainedWindowSheet> { 19 int returnCode_; 20 NSAlert* alert_; // weak 21 } 22 23 @property(nonatomic, readonly) int returnCode; 24 @property(nonatomic, assign) NSAlert* alert; 25 26 @end 27 28 @implementation ConstrainedWindowSystemSheetTest 29 30 @synthesize returnCode = returnCode_; 31 @synthesize alert = alert_; 32 33 - (void)showSheetForWindow:(NSWindow*)window { 34 [alert_ beginSheetModalForWindow:window 35 modalDelegate:self 36 didEndSelector:@selector(alertDidEnd:returnCode:ctxInfo:) 37 contextInfo:NULL]; 38 } 39 40 - (void)closeSheetWithAnimation:(BOOL)withAnimation { 41 [NSApp endSheet:[alert_ window] returnCode:kSystemSheetReturnCode]; 42 } 43 44 - (void)hideSheet { 45 } 46 47 - (void)unhideSheet { 48 } 49 50 - (void)pulseSheet { 51 } 52 53 - (void)makeSheetKeyAndOrderFront { 54 } 55 56 - (void)updateSheetPosition { 57 } 58 59 - (void)alertDidEnd:(NSAlert *)alert 60 returnCode:(NSInteger)returnCode 61 ctxInfo:(void *)contextInfo { 62 returnCode_ = returnCode; 63 } 64 65 @end 66 67 class ConstrainedWindowSheetControllerTest : public CocoaTest { 68 protected: 69 virtual ~ConstrainedWindowSheetControllerTest() { 70 } 71 72 virtual void SetUp() OVERRIDE { 73 CocoaTest::SetUp(); 74 75 // Center the window so that the sheet doesn't go offscreen. 76 [test_window() center]; 77 78 // Create two dummy tabs and make the first one active. 79 NSRect dummyRect = NSMakeRect(0, 0, 50, 50); 80 tab_views_.reset([[NSMutableArray alloc] init]); 81 for (int i = 0; i < 2; ++i) { 82 base::scoped_nsobject<NSView> view( 83 [[NSView alloc] initWithFrame:dummyRect]); 84 [tab_views_ addObject:view]; 85 } 86 tab0_.reset([[tab_views_ objectAtIndex:0] retain]); 87 tab1_.reset([[tab_views_ objectAtIndex:1] retain]); 88 ActivateTabView(tab0_); 89 90 // Create a test sheet. 91 sheet_window_.reset([[NSWindow alloc] 92 initWithContentRect:dummyRect 93 styleMask:NSTitledWindowMask 94 backing:NSBackingStoreBuffered 95 defer:NO]); 96 [sheet_window_ setReleasedWhenClosed:NO]; 97 sheet_.reset([[CustomConstrainedWindowSheet alloc] 98 initWithCustomWindow:sheet_window_]); 99 100 controller_.reset([[ConstrainedWindowSheetController 101 controllerForParentWindow:test_window()] retain]); 102 EXPECT_TRUE(controller_); 103 EXPECT_FALSE([ConstrainedWindowSheetController controllerForSheet:sheet_]); 104 } 105 106 virtual void TearDown() OVERRIDE { 107 sheet_.reset(); 108 sheet_window_.reset(); 109 CocoaTest::TearDown(); 110 } 111 112 void ActivateTabView(NSView* tab_view) { 113 for (NSView* view in tab_views_.get()) { 114 [view removeFromSuperview]; 115 } 116 [[test_window() contentView] addSubview:tab_view]; 117 active_tab_view_.reset([tab_view retain]); 118 119 ConstrainedWindowSheetController* controller = 120 [ConstrainedWindowSheetController 121 controllerForParentWindow:test_window()]; 122 EXPECT_TRUE(controller); 123 [controller parentViewDidBecomeActive:active_tab_view_]; 124 } 125 126 NSRect GetViewFrameInScreenCoordinates(NSView* view) { 127 NSRect rect = [view convertRect:[view bounds] toView:nil]; 128 rect.origin = [[view window] convertBaseToScreen:rect.origin]; 129 return rect; 130 } 131 132 void VerifySheetXPosition(NSRect sheet_frame, NSView* parent_view) { 133 NSRect parent_frame = GetViewFrameInScreenCoordinates(parent_view); 134 CGFloat expected_x = NSMinX(parent_frame) + 135 (NSWidth(parent_frame) - NSWidth(sheet_frame)) / 2.0; 136 EXPECT_EQ(expected_x, NSMinX(sheet_frame)); 137 } 138 139 CGFloat GetSheetYOffset(NSRect sheet_frame, NSView* parent_view) { 140 return NSMaxY(sheet_frame) - 141 NSMaxY(GetViewFrameInScreenCoordinates(parent_view)); 142 } 143 144 base::scoped_nsobject<NSWindow> sheet_window_; 145 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet_; 146 base::scoped_nsobject<ConstrainedWindowSheetController> controller_; 147 base::scoped_nsobject<NSMutableArray> tab_views_; 148 base::scoped_nsobject<NSView> active_tab_view_; 149 base::scoped_nsobject<NSView> tab0_; 150 base::scoped_nsobject<NSView> tab1_; 151 }; 152 153 // Test showing then hiding the sheet. 154 TEST_F(ConstrainedWindowSheetControllerTest, ShowHide) { 155 EXPECT_FALSE([sheet_window_ isVisible]); 156 [controller_ showSheet:sheet_ forParentView:active_tab_view_]; 157 EXPECT_TRUE([ConstrainedWindowSheetController controllerForSheet:sheet_]); 158 EXPECT_TRUE([sheet_window_ isVisible]); 159 160 [controller_ closeSheet:sheet_]; 161 EXPECT_FALSE([ConstrainedWindowSheetController controllerForSheet:sheet_]); 162 EXPECT_FALSE([sheet_window_ isVisible]); 163 } 164 165 // Test that switching tabs correctly hides the inactive tab's sheet. 166 TEST_F(ConstrainedWindowSheetControllerTest, SwitchTabs) { 167 [controller_ showSheet:sheet_ forParentView:active_tab_view_]; 168 169 EXPECT_TRUE([sheet_window_ isVisible]); 170 EXPECT_EQ(1.0, [sheet_window_ alphaValue]); 171 ActivateTabView([tab_views_ objectAtIndex:1]); 172 EXPECT_TRUE([sheet_window_ isVisible]); 173 EXPECT_EQ(0.0, [sheet_window_ alphaValue]); 174 ActivateTabView([tab_views_ objectAtIndex:0]); 175 EXPECT_TRUE([sheet_window_ isVisible]); 176 EXPECT_EQ(1.0, [sheet_window_ alphaValue]); 177 } 178 179 // Test that adding a sheet to an inactive view doesn't show it. 180 TEST_F(ConstrainedWindowSheetControllerTest, AddToInactiveTab) { 181 ActivateTabView(tab0_); 182 [controller_ showSheet:sheet_ forParentView:tab1_]; 183 EXPECT_EQ(0.0, [sheet_window_ alphaValue]); 184 185 ActivateTabView(tab1_); 186 EXPECT_EQ(1.0, [sheet_window_ alphaValue]); 187 VerifySheetXPosition([sheet_window_ frame], tab1_); 188 } 189 190 // Test that two parent windows with two sheet controllers don't conflict. 191 TEST_F(ConstrainedWindowSheetControllerTest, TwoParentWindows) { 192 base::scoped_nsobject<NSWindow> parent_window2( 193 [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 30, 30) 194 styleMask:NSTitledWindowMask 195 backing:NSBackingStoreBuffered 196 defer:NO]); 197 [parent_window2 setReleasedWhenClosed:NO]; 198 199 ConstrainedWindowSheetController* controller2 = 200 [ConstrainedWindowSheetController 201 controllerForParentWindow:parent_window2]; 202 EXPECT_TRUE(controller2); 203 EXPECT_NSNE(controller_, controller2); 204 205 [controller2 showSheet:sheet_ forParentView:[parent_window2 contentView]]; 206 EXPECT_NSEQ(controller2, 207 [ConstrainedWindowSheetController controllerForSheet:sheet_]); 208 209 [parent_window2 close]; 210 } 211 212 // Test that using a top level parent view works. 213 TEST_F(ConstrainedWindowSheetControllerTest, TopLevelView) { 214 NSView* parentView = [[test_window() contentView] superview]; 215 [controller_ parentViewDidBecomeActive:parentView]; 216 217 EXPECT_FALSE([sheet_window_ isVisible]); 218 [controller_ showSheet:sheet_ forParentView:parentView]; 219 EXPECT_TRUE([ConstrainedWindowSheetController controllerForSheet:sheet_]); 220 EXPECT_TRUE([sheet_window_ isVisible]); 221 VerifySheetXPosition([sheet_window_ frame], parentView); 222 } 223 224 // Test that resizing sheet works. 225 TEST_F(ConstrainedWindowSheetControllerTest, Resize) { 226 [controller_ showSheet:sheet_ forParentView:active_tab_view_]; 227 228 NSRect old_frame = [sheet_window_ frame]; 229 230 NSRect sheet_frame; 231 sheet_frame.size = NSMakeSize(NSWidth(old_frame) + 100, 232 NSHeight(old_frame) + 50); 233 sheet_frame.origin = [controller_ originForSheet:sheet_ 234 withWindowSize:sheet_frame.size]; 235 236 // Y pos should not have changed. 237 EXPECT_EQ(NSMaxY(sheet_frame), NSMaxY(old_frame)); 238 239 // X pos should be centered on parent view. 240 VerifySheetXPosition(sheet_frame, active_tab_view_); 241 } 242 243 // Test that resizing a hidden sheet works. 244 TEST_F(ConstrainedWindowSheetControllerTest, ResizeHiddenSheet) { 245 [controller_ showSheet:sheet_ forParentView:tab0_]; 246 EXPECT_EQ(1.0, [sheet_window_ alphaValue]); 247 ActivateTabView(tab1_); 248 EXPECT_EQ(0.0, [sheet_window_ alphaValue]); 249 250 NSRect old_frame = [sheet_window_ frame]; 251 NSRect new_inactive_frame = NSInsetRect(old_frame, -30, -40); 252 [sheet_window_ setFrame:new_inactive_frame display:YES]; 253 254 ActivateTabView(tab0_); 255 EXPECT_EQ(1.0, [sheet_window_ alphaValue]); 256 257 NSRect new_active_frame = [sheet_window_ frame]; 258 EXPECT_EQ(NSWidth(new_inactive_frame), NSWidth(new_active_frame)); 259 EXPECT_EQ(NSHeight(new_inactive_frame), NSHeight(new_active_frame)); 260 } 261 262 // Test resizing parent window keeps the sheet anchored. 263 TEST_F(ConstrainedWindowSheetControllerTest, ResizeParentWindow) { 264 [controller_ showSheet:sheet_ forParentView:active_tab_view_]; 265 CGFloat sheet_offset = 266 GetSheetYOffset([sheet_window_ frame], active_tab_view_); 267 268 // Test 3x3 different parent window sizes. 269 CGFloat insets[] = {-10, 0, 10}; 270 NSRect old_frame = [test_window() frame]; 271 272 for (size_t x = 0; x < sizeof(insets) / sizeof(CGFloat); x++) { 273 for (size_t y = 0; y < sizeof(insets) / sizeof(CGFloat); y++) { 274 NSRect resized_frame = NSInsetRect(old_frame, insets[x], insets[y]); 275 [test_window() setFrame:resized_frame display:YES]; 276 NSRect sheet_frame = [sheet_window_ frame]; 277 278 // Y pos should track parent view's position. 279 EXPECT_EQ(sheet_offset, GetSheetYOffset(sheet_frame, active_tab_view_)); 280 281 // X pos should be centered on parent view. 282 VerifySheetXPosition(sheet_frame, active_tab_view_); 283 } 284 } 285 } 286 287 // Test system sheets. 288 TEST_F(ConstrainedWindowSheetControllerTest, SystemSheet) { 289 base::scoped_nsobject<ConstrainedWindowSystemSheetTest> system_sheet( 290 [[ConstrainedWindowSystemSheetTest alloc] init]); 291 base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]); 292 [system_sheet setAlert:alert]; 293 294 EXPECT_FALSE([[alert window] isVisible]); 295 [controller_ showSheet:system_sheet 296 forParentView:active_tab_view_]; 297 EXPECT_TRUE([[alert window] isVisible]); 298 299 [controller_ closeSheet:system_sheet]; 300 EXPECT_FALSE([[alert window] isVisible]); 301 EXPECT_EQ(kSystemSheetReturnCode, [system_sheet returnCode]); 302 } 303 304 // Test showing a system sheet on an inactive tab. 305 TEST_F(ConstrainedWindowSheetControllerTest, SystemSheetAddToInactiveTab) { 306 base::scoped_nsobject<ConstrainedWindowSystemSheetTest> system_sheet( 307 [[ConstrainedWindowSystemSheetTest alloc] init]); 308 base::scoped_nsobject<NSAlert> alert([[NSAlert alloc] init]); 309 [system_sheet setAlert:alert]; 310 311 EXPECT_FALSE([[alert window] isVisible]); 312 [controller_ showSheet:system_sheet 313 forParentView:tab1_]; 314 EXPECT_FALSE([[alert window] isVisible]); 315 316 ActivateTabView(tab1_); 317 EXPECT_TRUE([[alert window] isVisible]); 318 EXPECT_EQ(1.0, [[alert window] alphaValue]); 319 320 [controller_ closeSheet:system_sheet]; 321 } 322