Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / base_bubble_controller_unittest.mm
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/base_bubble_controller.h"
6
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"
13
14 namespace {
15 const CGFloat kBubbleWindowWidth = 100;
16 const CGFloat kBubbleWindowHeight = 50;
17 const CGFloat kAnchorPointX = 400;
18 const CGFloat kAnchorPointY = 300;
19 }  // namespace
20
21 @interface ContextMenuController : NSObject<NSMenuDelegate> {
22  @private
23   NSMenu* menu_;
24   NSWindow* window_;
25   BOOL isMenuOpen_;
26   BOOL didOpen_;
27 }
28
29 - (id)initWithMenu:(NSMenu*)menu andWindow:(NSWindow*)window;
30
31 - (BOOL)isMenuOpen;
32 - (BOOL)didOpen;
33 - (BOOL)isWindowVisible;
34
35 // NSMenuDelegate methods
36 - (void)menuWillOpen:(NSMenu*)menu;
37 - (void)menuDidClose:(NSMenu*)menu;
38
39 @end
40
41 @implementation ContextMenuController
42
43 - (id)initWithMenu:(NSMenu*)menu andWindow:(NSWindow*)window {
44   if (self = [super init]) {
45     menu_ = menu;
46     window_ = window;
47     isMenuOpen_ = NO;
48     didOpen_ = NO;
49     [menu_ setDelegate:self];
50   }
51   return self;
52 }
53
54 - (BOOL)isMenuOpen {
55   return isMenuOpen_;
56 }
57
58 - (BOOL)didOpen {
59   return didOpen_;
60 }
61
62 - (BOOL)isWindowVisible {
63   if (window_) {
64     return [window_ isVisible];
65   }
66   return NO;
67 }
68
69 - (void)menuWillOpen:(NSMenu*)menu {
70   isMenuOpen_ = YES;
71   didOpen_ = NO;
72
73   NSArray* modes = @[NSEventTrackingRunLoopMode, NSDefaultRunLoopMode];
74   [menu_ performSelector:@selector(cancelTracking)
75               withObject:nil
76               afterDelay:0.1
77                  inModes:modes];
78 }
79
80 - (void)menuDidClose:(NSMenu*)menu {
81   isMenuOpen_ = NO;
82   didOpen_ = YES;
83 }
84
85 @end
86
87 class BaseBubbleControllerTest : public CocoaTest {
88  public:
89   BaseBubbleControllerTest() : controller_(nil) {}
90
91   virtual void SetUp() override {
92     bubble_window_.reset([[InfoBubbleWindow alloc]
93         initWithContentRect:NSMakeRect(0, 0, kBubbleWindowWidth,
94                                        kBubbleWindowHeight)
95                   styleMask:NSBorderlessWindowMask
96                     backing:NSBackingStoreBuffered
97                       defer:YES]);
98     [bubble_window_ setAllowedAnimations:0];
99
100     // The bubble controller will release itself when the window closes.
101     controller_ = [[BaseBubbleController alloc]
102         initWithWindow:bubble_window_
103           parentWindow:test_window()
104             anchoredAt:NSMakePoint(kAnchorPointX, kAnchorPointY)];
105     EXPECT_TRUE([controller_ bubble]);
106     EXPECT_EQ(bubble_window_.get(), [controller_ window]);
107   }
108
109   virtual void TearDown() override {
110     // Close our windows.
111     [controller_ close];
112     bubble_window_.reset();
113     CocoaTest::TearDown();
114   }
115
116   // Closing the bubble will autorelease the controller. Give callers a keep-
117   // alive to run checks after closing.
118   base::scoped_nsobject<BaseBubbleController> ShowBubble() WARN_UNUSED_RESULT {
119     base::scoped_nsobject<BaseBubbleController> keep_alive(
120         [controller_ retain]);
121     EXPECT_FALSE([bubble_window_ isVisible]);
122     [controller_ showWindow:nil];
123     EXPECT_TRUE([bubble_window_ isVisible]);
124     return keep_alive;
125   }
126
127   // Fake the key state notification. Because unit_tests is a "daemon" process
128   // type, its windows can never become key (nor can the app become active).
129   // Instead of the hacks below, one could make a browser_test or transform the
130   // process type, but this seems easiest and is best suited to a unit test.
131   //
132   // On Lion and above, which have the event taps, simply post a notification
133   // that will cause the controller to call |-windowDidResignKey:|. Earlier
134   // OSes can call through directly.
135   void SimulateKeyStatusChange() {
136     NSNotification* notif =
137         [NSNotification notificationWithName:NSWindowDidResignKeyNotification
138                                       object:[controller_ window]];
139     if (base::mac::IsOSLionOrLater())
140       [[NSNotificationCenter defaultCenter] postNotification:notif];
141     else
142       [controller_ windowDidResignKey:notif];
143   }
144
145  protected:
146   base::scoped_nsobject<InfoBubbleWindow> bubble_window_;
147   BaseBubbleController* controller_;
148
149  private:
150   DISALLOW_COPY_AND_ASSIGN(BaseBubbleControllerTest);
151 };
152
153 // Test that kAlignEdgeToAnchorEdge and a left bubble arrow correctly aligns the
154 // left edge of the buble to the anchor point.
155 TEST_F(BaseBubbleControllerTest, LeftAlign) {
156   [[controller_ bubble] setArrowLocation:info_bubble::kTopLeft];
157   [[controller_ bubble] setAlignment:info_bubble::kAlignEdgeToAnchorEdge];
158   [controller_ showWindow:nil];
159
160   NSRect frame = [[controller_ window] frame];
161   // Make sure the bubble size hasn't changed.
162   EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
163   EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
164   // Make sure the bubble is left aligned.
165   EXPECT_EQ(NSMinX(frame), kAnchorPointX);
166   EXPECT_GE(NSMaxY(frame), kAnchorPointY);
167 }
168
169 // Test that kAlignEdgeToAnchorEdge and a right bubble arrow correctly aligns
170 // the right edge of the buble to the anchor point.
171 TEST_F(BaseBubbleControllerTest, RightAlign) {
172   [[controller_ bubble] setArrowLocation:info_bubble::kTopRight];
173   [[controller_ bubble] setAlignment:info_bubble::kAlignEdgeToAnchorEdge];
174   [controller_ showWindow:nil];
175
176   NSRect frame = [[controller_ window] frame];
177   // Make sure the bubble size hasn't changed.
178   EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
179   EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
180   // Make sure the bubble is left aligned.
181   EXPECT_EQ(NSMaxX(frame), kAnchorPointX);
182   EXPECT_GE(NSMaxY(frame), kAnchorPointY);
183 }
184
185 // Test that kAlignArrowToAnchor and a left bubble arrow correctly aligns
186 // the bubble arrow to the anchor point.
187 TEST_F(BaseBubbleControllerTest, AnchorAlignLeftArrow) {
188   [[controller_ bubble] setArrowLocation:info_bubble::kTopLeft];
189   [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
190   [controller_ showWindow:nil];
191
192   NSRect frame = [[controller_ window] frame];
193   // Make sure the bubble size hasn't changed.
194   EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
195   EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
196   // Make sure the bubble arrow points to the anchor.
197   EXPECT_EQ(NSMinX(frame) + info_bubble::kBubbleArrowXOffset +
198       roundf(info_bubble::kBubbleArrowWidth / 2.0), kAnchorPointX);
199   EXPECT_GE(NSMaxY(frame), kAnchorPointY);
200 }
201
202 // Test that kAlignArrowToAnchor and a right bubble arrow correctly aligns
203 // the bubble arrow to the anchor point.
204 TEST_F(BaseBubbleControllerTest, AnchorAlignRightArrow) {
205   [[controller_ bubble] setArrowLocation:info_bubble::kTopRight];
206   [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
207   [controller_ showWindow:nil];
208
209   NSRect frame = [[controller_ window] frame];
210   // Make sure the bubble size hasn't changed.
211   EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
212   EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
213   // Make sure the bubble arrow points to the anchor.
214   EXPECT_EQ(NSMaxX(frame) - info_bubble::kBubbleArrowXOffset -
215       floorf(info_bubble::kBubbleArrowWidth / 2.0), kAnchorPointX);
216   EXPECT_GE(NSMaxY(frame), kAnchorPointY);
217 }
218
219 // Test that kAlignArrowToAnchor and a center bubble arrow correctly align
220 // the bubble towards the anchor point.
221 TEST_F(BaseBubbleControllerTest, AnchorAlignCenterArrow) {
222   [[controller_ bubble] setArrowLocation:info_bubble::kTopCenter];
223   [[controller_ bubble] setAlignment:info_bubble::kAlignArrowToAnchor];
224   [controller_ showWindow:nil];
225
226   NSRect frame = [[controller_ window] frame];
227   // Make sure the bubble size hasn't changed.
228   EXPECT_EQ(frame.size.width, kBubbleWindowWidth);
229   EXPECT_EQ(frame.size.height, kBubbleWindowHeight);
230   // Make sure the bubble arrow points to the anchor.
231   EXPECT_EQ(NSMidX(frame), kAnchorPointX);
232   EXPECT_GE(NSMaxY(frame), kAnchorPointY);
233 }
234
235 // Test that the window is given an initial position before being shown. This
236 // ensures offscreen initialization is done using correct screen metrics.
237 TEST_F(BaseBubbleControllerTest, PositionedBeforeShow) {
238   // Verify default alignment settings, used when initialized in SetUp().
239   EXPECT_EQ(info_bubble::kTopRight, [[controller_ bubble] arrowLocation]);
240   EXPECT_EQ(info_bubble::kAlignArrowToAnchor, [[controller_ bubble] alignment]);
241
242   // Verify the default frame (positioned relative to the test_window() origin).
243   NSRect frame = [[controller_ window] frame];
244   EXPECT_EQ(NSMaxX(frame) - info_bubble::kBubbleArrowXOffset -
245       floorf(info_bubble::kBubbleArrowWidth / 2.0), kAnchorPointX);
246   EXPECT_EQ(NSMaxY(frame), kAnchorPointY);
247 }
248
249 // Tests that when a new window gets key state (and the bubble resigns) that
250 // the key window changes.
251 TEST_F(BaseBubbleControllerTest, ResignKeyCloses) {
252   base::scoped_nsobject<NSWindow> other_window(
253       [[NSWindow alloc] initWithContentRect:NSMakeRect(500, 500, 500, 500)
254                                   styleMask:NSTitledWindowMask
255                                     backing:NSBackingStoreBuffered
256                                       defer:YES]);
257
258   base::scoped_nsobject<BaseBubbleController> keep_alive = ShowBubble();
259   EXPECT_FALSE([other_window isVisible]);
260
261   [other_window makeKeyAndOrderFront:nil];
262   SimulateKeyStatusChange();
263
264   EXPECT_FALSE([bubble_window_ isVisible]);
265   EXPECT_TRUE([other_window isVisible]);
266 }
267
268 // Test that clicking outside the window causes the bubble to close if
269 // shouldCloseOnResignKey is YES.
270 TEST_F(BaseBubbleControllerTest, LionClickOutsideClosesWithoutContextMenu) {
271   // The event tap is only installed on 10.7+.
272   if (!base::mac::IsOSLionOrLater())
273     return;
274
275   base::scoped_nsobject<BaseBubbleController> keep_alive = ShowBubble();
276   NSWindow* window = [controller_ window];
277
278   EXPECT_TRUE([controller_ shouldCloseOnResignKey]);  // Verify default value.
279   [controller_ setShouldCloseOnResignKey:NO];
280   NSEvent* event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
281       NSMakePoint(10, 10), test_window());
282   [NSApp sendEvent:event];
283
284   EXPECT_TRUE([window isVisible]);
285
286   event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
287       NSMakePoint(10, 10), test_window());
288   [NSApp sendEvent:event];
289
290   EXPECT_TRUE([window isVisible]);
291
292   [controller_ setShouldCloseOnResignKey:YES];
293   event = cocoa_test_event_utils::LeftMouseDownAtPointInWindow(
294       NSMakePoint(10, 10), test_window());
295   [NSApp sendEvent:event];
296
297   EXPECT_FALSE([window isVisible]);
298
299   [controller_ showWindow:nil]; // Show it again
300   EXPECT_TRUE([window isVisible]);
301   EXPECT_TRUE([controller_ shouldCloseOnResignKey]);  // Verify.
302
303   event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
304       NSMakePoint(10, 10), test_window());
305   [NSApp sendEvent:event];
306
307   EXPECT_FALSE([window isVisible]);
308 }
309
310 // Test that right-clicking the window with displaying a context menu causes
311 // the bubble  to close.
312 TEST_F(BaseBubbleControllerTest, LionRightClickOutsideClosesWithContextMenu) {
313   // The event tap is only installed on 10.7+.
314   if (!base::mac::IsOSLionOrLater())
315     return;
316
317   base::scoped_nsobject<BaseBubbleController> keep_alive = ShowBubble();
318   NSWindow* window = [controller_ window];
319
320   base::scoped_nsobject<NSMenu> context_menu(
321       [[NSMenu alloc] initWithTitle:@""]);
322   [context_menu addItemWithTitle:@"ContextMenuTest"
323                           action:nil
324                    keyEquivalent:@""];
325   base::scoped_nsobject<ContextMenuController> menu_controller(
326       [[ContextMenuController alloc] initWithMenu:context_menu
327                                         andWindow:window]);
328
329   // Set the menu as the contextual menu of contentView of test_window().
330   [[test_window() contentView] setMenu:context_menu];
331
332   // RightMouseDown in test_window() would close the bubble window and then
333   // dispaly the contextual menu.
334   NSEvent* event = cocoa_test_event_utils::RightMouseDownAtPointInWindow(
335       NSMakePoint(10, 10), test_window());
336   // Verify bubble's window is closed when contextual menu is open.
337   CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
338       EXPECT_TRUE([menu_controller isMenuOpen]);
339       EXPECT_FALSE([menu_controller isWindowVisible]);
340   });
341
342   EXPECT_FALSE([menu_controller isMenuOpen]);
343   EXPECT_FALSE([menu_controller didOpen]);
344
345   [NSApp sendEvent:event];
346
347   // When we got here, menu has already run its RunLoop.
348   // See -[ContextualMenuController menuWillOpen:].
349   EXPECT_FALSE([window isVisible]);
350
351   EXPECT_FALSE([menu_controller isMenuOpen]);
352   EXPECT_TRUE([menu_controller didOpen]);
353 }
354
355 // Test that the bubble is not dismissed when it has an attached sheet, or when
356 // a sheet loses key status (since the sheet is not attached when that happens).
357 TEST_F(BaseBubbleControllerTest, BubbleStaysOpenWithSheet) {
358   base::scoped_nsobject<BaseBubbleController> keep_alive = ShowBubble();
359
360   // Make a dummy NSPanel for the sheet. Don't use [NSOpenPanel openPanel],
361   // otherwise a stray FI_TFloatingInputWindow is created which the unit test
362   // harness doesn't like.
363   base::scoped_nsobject<NSPanel> panel(
364       [[NSPanel alloc] initWithContentRect:NSMakeRect(0, 0, 100, 50)
365                                  styleMask:NSTitledWindowMask
366                                    backing:NSBackingStoreBuffered
367                                      defer:YES]);
368   EXPECT_FALSE([panel isReleasedWhenClosed]);  // scoped_nsobject releases it.
369
370   // With a NSOpenPanel, we would call -[NSSavePanel beginSheetModalForWindow]
371   // here. In 10.9, we would call [NSWindow beginSheet:]. For 10.6, this:
372   [[NSApplication sharedApplication] beginSheet:panel
373                                  modalForWindow:bubble_window_
374                                   modalDelegate:nil
375                                  didEndSelector:NULL
376                                     contextInfo:NULL];
377
378   EXPECT_TRUE([bubble_window_ isVisible]);
379   EXPECT_TRUE([panel isVisible]);
380   // Losing key status while there is an attached window should not close the
381   // bubble.
382   SimulateKeyStatusChange();
383   EXPECT_TRUE([bubble_window_ isVisible]);
384   EXPECT_TRUE([panel isVisible]);
385
386   // Closing the attached sheet should not close the bubble.
387   [[NSApplication sharedApplication] endSheet:panel];
388   [panel close];
389
390   EXPECT_FALSE([bubble_window_ attachedSheet]);
391   EXPECT_TRUE([bubble_window_ isVisible]);
392   EXPECT_FALSE([panel isVisible]);
393
394   // Now that the sheet is gone, a key status change should close the bubble.
395   SimulateKeyStatusChange();
396   EXPECT_FALSE([bubble_window_ isVisible]);
397 }