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.
5 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
7 #include "base/mac/mac_util.h"
8 #import "base/mac/scoped_nsobject.h"
9 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
10 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
11 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
12 #import "ui/events/test/cocoa_test_event_utils.h"
15 const CGFloat kBubbleWindowWidth = 100;
16 const CGFloat kBubbleWindowHeight = 50;
17 const CGFloat kAnchorPointX = 400;
18 const CGFloat kAnchorPointY = 300;
21 @interface ContextMenuController : NSObject<NSMenuDelegate> {
29 - (id)initWithMenu:(NSMenu*)menu andWindow:(NSWindow*)window;
33 - (BOOL)isWindowVisible;
35 // NSMenuDelegate methods
36 - (void)menuWillOpen:(NSMenu*)menu;
37 - (void)menuDidClose:(NSMenu*)menu;
41 @implementation ContextMenuController
43 - (id)initWithMenu:(NSMenu*)menu andWindow:(NSWindow*)window {
44 if (self = [super init]) {
49 [menu_ setDelegate:self];
62 - (BOOL)isWindowVisible {
64 return [window_ isVisible];
69 - (void)menuWillOpen:(NSMenu*)menu {
73 NSArray* modes = @[NSEventTrackingRunLoopMode, NSDefaultRunLoopMode];
74 [menu_ performSelector:@selector(cancelTracking)
80 - (void)menuDidClose:(NSMenu*)menu {
87 class BaseBubbleControllerTest : public CocoaTest {
89 virtual void SetUp() OVERRIDE {
90 bubbleWindow_.reset([[InfoBubbleWindow alloc]
91 initWithContentRect:NSMakeRect(0, 0, kBubbleWindowWidth,
93 styleMask:NSBorderlessWindowMask
94 backing:NSBackingStoreBuffered
96 [bubbleWindow_ setAllowedAnimations:0];
98 // The bubble controller will release itself when the window closes.
99 controller_ = [[BaseBubbleController alloc]
100 initWithWindow:bubbleWindow_.get()
101 parentWindow:test_window()
102 anchoredAt:NSMakePoint(kAnchorPointX, kAnchorPointY)];
103 EXPECT_TRUE([controller_ bubble]);
106 virtual void TearDown() OVERRIDE {
107 // Close our windows.
109 bubbleWindow_.reset(NULL);
110 CocoaTest::TearDown();
114 base::scoped_nsobject<InfoBubbleWindow> bubbleWindow_;
115 BaseBubbleController* controller_;
118 // Test that kAlignEdgeToAnchorEdge and a left bubble arrow correctly aligns the
119 // left edge of the buble to the anchor point.
120 TEST_F(BaseBubbleControllerTest, LeftAlign) {
121 [[controller_ bubble] setArrowLocation:info_bubble::kTopLeft];
122 [[controller_ bubble] setAlignment:info_bubble::kAlignEdgeToAnchorEdge];
123 [controller_ showWindow:nil];
125 NSRect frame = [[controller_ window] frame];
126 // Make sure the bubble size hasn't changed.
127 EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
128 EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
129 // Make sure the bubble is left aligned.
130 EXPECT_EQ(NSMinX(frame), kAnchorPointX);
131 EXPECT_GE(NSMaxY(frame), kAnchorPointY);
134 // Test that kAlignEdgeToAnchorEdge and a right bubble arrow correctly aligns
135 // the right edge of the buble to the anchor point.
136 TEST_F(BaseBubbleControllerTest, RightAlign) {
137 [[controller_ bubble] setArrowLocation:info_bubble::kTopRight];
138 [[controller_ bubble] setAlignment:info_bubble::kAlignEdgeToAnchorEdge];
139 [controller_ showWindow:nil];
141 NSRect frame = [[controller_ window] frame];
142 // Make sure the bubble size hasn't changed.
143 EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
144 EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
145 // Make sure the bubble is left aligned.
146 EXPECT_EQ(NSMaxX(frame), kAnchorPointX);
147 EXPECT_GE(NSMaxY(frame), kAnchorPointY);
150 // Test that kAlignArrowToAnchor and a left bubble arrow correctly aligns
151 // the bubble arrow to the anchor point.
152 TEST_F(BaseBubbleControllerTest, AnchorAlignLeftArrow) {
153 [[controller_ bubble] setArrowLocation:info_bubble::kTopLeft];
154 [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
155 [controller_ showWindow:nil];
157 NSRect frame = [[controller_ window] frame];
158 // Make sure the bubble size hasn't changed.
159 EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
160 EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
161 // Make sure the bubble arrow points to the anchor.
162 EXPECT_EQ(NSMinX(frame) + info_bubble::kBubbleArrowXOffset +
163 roundf(info_bubble::kBubbleArrowWidth / 2.0), kAnchorPointX);
164 EXPECT_GE(NSMaxY(frame), kAnchorPointY);
167 // Test that kAlignArrowToAnchor and a right bubble arrow correctly aligns
168 // the bubble arrow to the anchor point.
169 TEST_F(BaseBubbleControllerTest, AnchorAlignRightArrow) {
170 [[controller_ bubble] setArrowLocation:info_bubble::kTopRight];
171 [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
172 [controller_ showWindow:nil];
174 NSRect frame = [[controller_ window] frame];
175 // Make sure the bubble size hasn't changed.
176 EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
177 EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
178 // Make sure the bubble arrow points to the anchor.
179 EXPECT_EQ(NSMaxX(frame) - info_bubble::kBubbleArrowXOffset -
180 floorf(info_bubble::kBubbleArrowWidth / 2.0), kAnchorPointX);
181 EXPECT_GE(NSMaxY(frame), kAnchorPointY);
184 // Test that kAlignArrowToAnchor and a center bubble arrow correctly align
185 // the bubble towards the anchor point.
186 TEST_F(BaseBubbleControllerTest, AnchorAlignCenterArrow) {
187 [[controller_ bubble] setArrowLocation:info_bubble::kTopCenter];
188 [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
189 [controller_ showWindow:nil];
191 NSRect frame = [[controller_ window] frame];
192 // Make sure the bubble size hasn't changed.
193 EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
194 EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
195 // Make sure the bubble arrow points to the anchor.
196 EXPECT_EQ(NSMidX(frame), kAnchorPointX);
197 EXPECT_GE(NSMaxY(frame), kAnchorPointY);
200 // Tests that when a new window gets key state (and the bubble resigns) that
201 // the key window changes.
202 TEST_F(BaseBubbleControllerTest, ResignKeyCloses) {
203 // Closing the bubble will autorelease the controller.
204 base::scoped_nsobject<BaseBubbleController> keep_alive([controller_ retain]);
206 NSWindow* bubble_window = [controller_ window];
207 EXPECT_FALSE([bubble_window isVisible]);
209 base::scoped_nsobject<NSWindow> other_window(
210 [[NSWindow alloc] initWithContentRect:NSMakeRect(500, 500, 500, 500)
211 styleMask:NSTitledWindowMask
212 backing:NSBackingStoreBuffered
214 EXPECT_FALSE([other_window isVisible]);
216 [controller_ showWindow:nil];
217 EXPECT_TRUE([bubble_window isVisible]);
218 EXPECT_FALSE([other_window isVisible]);
220 [other_window makeKeyAndOrderFront:nil];
221 // Fake the key state notification. Because unit_tests is a "daemon" process
222 // type, its windows can never become key (nor can the app become active).
223 // Instead of the hacks below, one could make a browser_test or transform the
224 // process type, but this seems easiest and is best suited to a unit test.
226 // On Lion and above, which have the event taps, simply post a notification
227 // that will cause the controller to call |-windowDidResignKey:|. Earlier
228 // OSes can call through directly.
229 NSNotification* notif =
230 [NSNotification notificationWithName:NSWindowDidResignKeyNotification
231 object:bubble_window];
232 if (base::mac::IsOSLionOrLater())
233 [[NSNotificationCenter defaultCenter] postNotification:notif];
235 [controller_ windowDidResignKey:notif];
238 EXPECT_FALSE([bubble_window isVisible]);
239 EXPECT_TRUE([other_window isVisible]);
242 // Test that clicking outside the window causes the bubble to close if
243 // shouldCloseOnResignKey is YES.
244 TEST_F(BaseBubbleControllerTest, LionClickOutsideClosesWithoutContextMenu) {
245 // The event tap is only installed on 10.7+.
246 if (!base::mac::IsOSLionOrLater())
249 // Closing the bubble will autorelease the controller.
250 base::scoped_nsobject<BaseBubbleController> keep_alive([controller_ retain]);
251 NSWindow* window = [controller_ window];
253 EXPECT_TRUE([controller_ shouldCloseOnResignKey]); // Verify default value.
254 EXPECT_FALSE([window isVisible]);
256 [controller_ showWindow:nil];
258 EXPECT_TRUE([window isVisible]);
260 [controller_ setShouldCloseOnResignKey:NO];
261 NSEvent* event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
262 NSMakePoint(10, 10), test_window());
263 [NSApp sendEvent:event];
265 EXPECT_TRUE([window isVisible]);
267 event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
268 NSMakePoint(10, 10), test_window());
269 [NSApp sendEvent:event];
271 EXPECT_TRUE([window isVisible]);
273 [controller_ setShouldCloseOnResignKey:YES];
274 event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
275 NSMakePoint(10, 10), test_window());
276 [NSApp sendEvent:event];
278 EXPECT_FALSE([window isVisible]);
280 [controller_ showWindow:nil]; // Show it again
281 EXPECT_TRUE([window isVisible]);
282 EXPECT_TRUE([controller_ shouldCloseOnResignKey]); // Verify.
284 event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
285 NSMakePoint(10, 10), test_window());
286 [NSApp sendEvent:event];
288 EXPECT_FALSE([window isVisible]);
291 // Test that right-clicking the window with displaying a context menu causes
292 // the bubble to close.
293 TEST_F(BaseBubbleControllerTest, LionRightClickOutsideClosesWithContextMenu) {
294 // The event tap is only installed on 10.7+.
295 if (!base::mac::IsOSLionOrLater())
298 // Closing the bubble will autorelease the controller.
299 base::scoped_nsobject<BaseBubbleController> keep_alive([controller_ retain]);
300 NSWindow* window = [controller_ window];
302 EXPECT_TRUE([controller_ shouldCloseOnResignKey]); // Verify default value.
303 EXPECT_FALSE([window isVisible]);
305 [controller_ showWindow:nil];
307 EXPECT_TRUE([window isVisible]);
309 base::scoped_nsobject<NSMenu> context_menu(
310 [[NSMenu alloc] initWithTitle:@""]);
311 [context_menu addItemWithTitle:@"ContextMenuTest"
314 base::scoped_nsobject<ContextMenuController> menu_controller(
315 [[ContextMenuController alloc] initWithMenu:context_menu
318 // Set the menu as the contextual menu of contentView of test_window().
319 [[test_window() contentView] setMenu:context_menu];
321 // RightMouseDown in test_window() would close the bubble window and then
322 // dispaly the contextual menu.
323 NSEvent* event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
324 NSMakePoint(10, 10), test_window());
325 // Verify bubble's window is closed when contextual menu is open.
326 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
327 EXPECT_TRUE([menu_controller isMenuOpen]);
328 EXPECT_FALSE([menu_controller isWindowVisible]);
331 EXPECT_FALSE([menu_controller isMenuOpen]);
332 EXPECT_FALSE([menu_controller didOpen]);
334 [NSApp sendEvent:event];
336 // When we got here, menu has already run its RunLoop.
337 // See -[ContextualMenuController menuWillOpen:].
338 EXPECT_FALSE([window isVisible]);
340 EXPECT_FALSE([menu_controller isMenuOpen]);
341 EXPECT_TRUE([menu_controller didOpen]);