Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / tabs / tab_controller_unittest.mm
1 // Copyright (c) 2011 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 <Cocoa/Cocoa.h>
6
7 #import "base/mac/scoped_nsobject.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
10 #import "chrome/browser/ui/cocoa/tabs/media_indicator_button.h"
11 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
12 #import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
13 #import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h"
14 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #import "testing/gtest_mac.h"
17 #include "testing/platform_test.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/resources/grit/ui_resources.h"
20
21 // Implements the target interface for the tab, which gets sent messages when
22 // the tab is clicked on by the user and when its close box is clicked.
23 @interface TabControllerTestTarget : NSObject<TabControllerTarget> {
24  @private
25   bool selected_;
26   bool closed_;
27   base::scoped_nsobject<TabStripDragController> dragController_;
28 }
29 - (bool)selected;
30 - (bool)closed;
31 @end
32
33 @implementation TabControllerTestTarget
34 - (id)init {
35   if ((self = [super init])) {
36     dragController_.reset(
37         [[TabStripDragController alloc] initWithTabStripController:nil]);
38   }
39   return self;
40 }
41 - (bool)selected {
42   return selected_;
43 }
44 - (bool)closed {
45   return closed_;
46 }
47 - (void)selectTab:(id)sender {
48   selected_ = true;
49 }
50 - (void)closeTab:(id)sender {
51   closed_ = true;
52 }
53 - (void)mouseTimer:(NSTimer*)timer {
54   // Fire the mouseUp to break the TabView drag loop.
55   NSEvent* current = [NSApp currentEvent];
56   NSWindow* window = [timer userInfo];
57   NSEvent* up = [NSEvent mouseEventWithType:NSLeftMouseUp
58                                    location:[current locationInWindow]
59                               modifierFlags:0
60                                   timestamp:[current timestamp]
61                                windowNumber:[window windowNumber]
62                                     context:nil
63                                 eventNumber:0
64                                  clickCount:1
65                                    pressure:1.0];
66   [window postEvent:up atStart:YES];
67 }
68 - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
69           forController:(TabController*)controller {
70 }
71 - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
72            forController:(TabController*)controller {
73   return NO;
74 }
75 - (ui::SimpleMenuModel*)contextMenuModelForController:(TabController*)controller
76     menuDelegate:(ui::SimpleMenuModel::Delegate*)delegate {
77   ui::SimpleMenuModel* model = new ui::SimpleMenuModel(delegate);
78   model->AddItem(1, base::ASCIIToUTF16("Hello World"));
79   model->AddItem(2, base::ASCIIToUTF16("Allays"));
80   model->AddItem(3, base::ASCIIToUTF16("Chromium"));
81   return model;
82 }
83 - (id<TabDraggingEventTarget>)dragController {
84   return dragController_.get();
85 }
86 @end
87
88 namespace {
89
90 CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) {
91   return NSMinX(subFrame) - NSMinX(superFrame);
92 }
93
94 CGFloat RightMargin(NSRect superFrame, NSRect subFrame) {
95   return NSMaxX(superFrame) - NSMaxX(subFrame);
96 }
97
98 // The dragging code in TabView makes heavy use of autorelease pools so
99 // inherit from CocoaTest to have one created for us.
100 class TabControllerTest : public CocoaTest {
101  public:
102   TabControllerTest() { }
103
104   static void CheckForExpectedLayoutAndVisibilityOfSubviews(
105       const TabController* controller) {
106     // Check whether subviews should be visible when they are supposed to be,
107     // given Tab size and TabRendererData state.
108     const TabMediaState indicatorState =
109         [[controller mediaIndicatorButton] showingMediaState];
110     if ([controller mini]) {
111       EXPECT_EQ(1, [controller iconCapacity]);
112       if (indicatorState != TAB_MEDIA_STATE_NONE) {
113         EXPECT_FALSE([controller shouldShowIcon]);
114         EXPECT_TRUE([controller shouldShowMediaIndicator]);
115       } else {
116         EXPECT_TRUE([controller shouldShowIcon]);
117         EXPECT_FALSE([controller shouldShowMediaIndicator]);
118       }
119       EXPECT_FALSE([controller shouldShowCloseButton]);
120     } else if ([controller selected]) {
121       EXPECT_TRUE([controller shouldShowCloseButton]);
122       switch ([controller iconCapacity]) {
123         case 0:
124         case 1:
125           EXPECT_FALSE([controller shouldShowIcon]);
126           EXPECT_FALSE([controller shouldShowMediaIndicator]);
127           break;
128         case 2:
129           if (indicatorState != TAB_MEDIA_STATE_NONE) {
130             EXPECT_FALSE([controller shouldShowIcon]);
131             EXPECT_TRUE([controller shouldShowMediaIndicator]);
132           } else {
133             EXPECT_TRUE([controller shouldShowIcon]);
134             EXPECT_FALSE([controller shouldShowMediaIndicator]);
135           }
136           break;
137         default:
138           EXPECT_LE(3, [controller iconCapacity]);
139           EXPECT_TRUE([controller shouldShowIcon]);
140           if (indicatorState != TAB_MEDIA_STATE_NONE)
141             EXPECT_TRUE([controller shouldShowMediaIndicator]);
142           else
143             EXPECT_FALSE([controller shouldShowMediaIndicator]);
144           break;
145       }
146     } else {  // Tab not selected/active and not mini tab.
147       switch ([controller iconCapacity]) {
148         case 0:
149           EXPECT_FALSE([controller shouldShowCloseButton]);
150           EXPECT_FALSE([controller shouldShowIcon]);
151           EXPECT_FALSE([controller shouldShowMediaIndicator]);
152           break;
153         case 1:
154           EXPECT_FALSE([controller shouldShowCloseButton]);
155           if (indicatorState != TAB_MEDIA_STATE_NONE) {
156             EXPECT_FALSE([controller shouldShowIcon]);
157             EXPECT_TRUE([controller shouldShowMediaIndicator]);
158           } else {
159             EXPECT_TRUE([controller shouldShowIcon]);
160             EXPECT_FALSE([controller shouldShowMediaIndicator]);
161           }
162           break;
163         default:
164           EXPECT_LE(2, [controller iconCapacity]);
165           EXPECT_TRUE([controller shouldShowIcon]);
166           if (indicatorState != TAB_MEDIA_STATE_NONE)
167             EXPECT_TRUE([controller shouldShowMediaIndicator]);
168           else
169             EXPECT_FALSE([controller shouldShowMediaIndicator]);
170           break;
171       }
172     }
173
174     // Make sure the NSView's "isHidden" state jives with the "shouldShowXXX."
175     EXPECT_TRUE([controller shouldShowIcon] ==
176                 (!![controller iconView] && ![[controller iconView] isHidden]));
177     EXPECT_TRUE([controller mini] == [[controller tabView] titleHidden]);
178     EXPECT_TRUE([controller shouldShowMediaIndicator] ==
179                     ![[controller mediaIndicatorButton] isHidden]);
180     EXPECT_TRUE([controller shouldShowCloseButton] !=
181                     [[controller closeButton] isHidden]);
182
183     // Check positioning of elements with respect to each other, and that they
184     // are fully within the tab frame.
185     const NSRect tabFrame = [[controller view] frame];
186     const NSRect titleFrame = [[controller tabView] titleFrame];
187     if ([controller shouldShowIcon]) {
188       const NSRect iconFrame = [[controller iconView] frame];
189       EXPECT_LE(NSMinX(tabFrame), NSMinX(iconFrame));
190       if (NSWidth(titleFrame) > 0)
191         EXPECT_LE(NSMaxX(iconFrame), NSMinX(titleFrame));
192       EXPECT_LE(NSMinY(tabFrame), NSMinY(iconFrame));
193       EXPECT_LE(NSMaxY(iconFrame), NSMaxY(tabFrame));
194     }
195     if ([controller shouldShowIcon] && [controller shouldShowMediaIndicator]) {
196       EXPECT_LE(NSMaxX([[controller iconView] frame]),
197                 NSMinX([[controller mediaIndicatorButton] frame]));
198     }
199     if ([controller shouldShowMediaIndicator]) {
200       const NSRect mediaIndicatorFrame =
201           [[controller mediaIndicatorButton] frame];
202       if (NSWidth(titleFrame) > 0)
203         EXPECT_LE(NSMaxX(titleFrame), NSMinX(mediaIndicatorFrame));
204       EXPECT_LE(NSMaxX(mediaIndicatorFrame), NSMaxX(tabFrame));
205       EXPECT_LE(NSMinY(tabFrame), NSMinY(mediaIndicatorFrame));
206       EXPECT_LE(NSMaxY(mediaIndicatorFrame), NSMaxY(tabFrame));
207     }
208     if ([controller shouldShowMediaIndicator] &&
209         [controller shouldShowCloseButton]) {
210       EXPECT_LE(NSMaxX([[controller mediaIndicatorButton] frame]),
211                 NSMinX([[controller closeButton] frame]));
212     }
213     if ([controller shouldShowCloseButton]) {
214       const NSRect closeButtonFrame = [[controller closeButton] frame];
215       if (NSWidth(titleFrame) > 0)
216         EXPECT_LE(NSMaxX(titleFrame), NSMinX(closeButtonFrame));
217       EXPECT_LE(NSMaxX(closeButtonFrame), NSMaxX(tabFrame));
218       EXPECT_LE(NSMinY(tabFrame), NSMinY(closeButtonFrame));
219       EXPECT_LE(NSMaxY(closeButtonFrame), NSMaxY(tabFrame));
220     }
221   }
222 };
223
224 // Tests creating the controller, sticking it in a window, and removing it.
225 TEST_F(TabControllerTest, Creation) {
226   NSWindow* window = test_window();
227   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
228   [[window contentView] addSubview:[controller view]];
229   EXPECT_TRUE([controller tabView]);
230   EXPECT_EQ([[controller view] window], window);
231   [[controller view] display];  // Test drawing to ensure nothing leaks/crashes.
232   [[controller view] removeFromSuperview];
233 }
234
235 // Tests sending it a close message and ensuring that the target/action get
236 // called. Mimics the user clicking on the close button in the tab.
237 TEST_F(TabControllerTest, Close) {
238   NSWindow* window = test_window();
239   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
240   [[window contentView] addSubview:[controller view]];
241
242   base::scoped_nsobject<TabControllerTestTarget> target(
243       [[TabControllerTestTarget alloc] init]);
244   EXPECT_FALSE([target closed]);
245   [controller setTarget:target];
246   EXPECT_EQ(target.get(), [controller target]);
247
248   [controller closeTab:nil];
249   EXPECT_TRUE([target closed]);
250
251   [[controller view] removeFromSuperview];
252 }
253
254 // Tests setting the |selected| property via code.
255 TEST_F(TabControllerTest, APISelection) {
256   NSWindow* window = test_window();
257   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
258   [[window contentView] addSubview:[controller view]];
259
260   EXPECT_FALSE([controller selected]);
261   [controller setSelected:YES];
262   EXPECT_TRUE([controller selected]);
263
264   [[controller view] removeFromSuperview];
265 }
266
267 // Tests setting the |loading| property via code.
268 TEST_F(TabControllerTest, Loading) {
269   NSWindow* window = test_window();
270   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
271   [[window contentView] addSubview:[controller view]];
272
273   EXPECT_EQ(kTabDone, [controller loadingState]);
274   [controller setLoadingState:kTabWaiting];
275   EXPECT_EQ(kTabWaiting, [controller loadingState]);
276   [controller setLoadingState:kTabLoading];
277   EXPECT_EQ(kTabLoading, [controller loadingState]);
278   [controller setLoadingState:kTabDone];
279   EXPECT_EQ(kTabDone, [controller loadingState]);
280
281   [[controller view] removeFromSuperview];
282 }
283
284 // Tests selecting the tab with the mouse click and ensuring the target/action
285 // get called.
286 TEST_F(TabControllerTest, UserSelection) {
287   NSWindow* window = test_window();
288
289   // Create a tab at a known location in the window that we can click on
290   // to activate selection.
291   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
292   [[window contentView] addSubview:[controller view]];
293   NSRect frame = [[controller view] frame];
294   frame.size.width = [TabController minTabWidth];
295   frame.origin = NSZeroPoint;
296   [[controller view] setFrame:frame];
297
298   // Set the target and action.
299   base::scoped_nsobject<TabControllerTestTarget> target(
300       [[TabControllerTestTarget alloc] init]);
301   EXPECT_FALSE([target selected]);
302   [controller setTarget:target];
303   [controller setAction:@selector(selectTab:)];
304   EXPECT_EQ(target.get(), [controller target]);
305   EXPECT_EQ(@selector(selectTab:), [controller action]);
306
307   // In order to track a click, we have to fake a mouse down and a mouse
308   // up, but the down goes into a tight drag loop. To break the loop, we have
309   // to fire a timer that sends a mouse up event while the "drag" is ongoing.
310   [NSTimer scheduledTimerWithTimeInterval:0.1
311                                    target:target.get()
312                                  selector:@selector(mouseTimer:)
313                                  userInfo:window
314                                   repeats:NO];
315   NSEvent* current = [NSApp currentEvent];
316   NSPoint click_point = NSMakePoint(frame.size.width / 2,
317                                     frame.size.height / 2);
318   NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown
319                                      location:click_point
320                                 modifierFlags:0
321                                     timestamp:[current timestamp]
322                                  windowNumber:[window windowNumber]
323                                       context:nil
324                                   eventNumber:0
325                                    clickCount:1
326                                      pressure:1.0];
327   [[controller view] mouseDown:down];
328
329   // Check our target was told the tab got selected.
330   EXPECT_TRUE([target selected]);
331
332   [[controller view] removeFromSuperview];
333 }
334
335 TEST_F(TabControllerTest, IconCapacity) {
336   NSWindow* window = test_window();
337   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
338   [[window contentView] addSubview:[controller view]];
339   int cap = [controller iconCapacity];
340   EXPECT_GE(cap, 1);
341
342   NSRect frame = [[controller view] frame];
343   frame.size.width += 500;
344   [[controller view] setFrame:frame];
345   int newcap = [controller iconCapacity];
346   EXPECT_GT(newcap, cap);
347 }
348
349 TEST_F(TabControllerTest, ShouldShowIcon) {
350   NSWindow* window = test_window();
351   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
352   [[window contentView] addSubview:[controller view]];
353   int cap = [controller iconCapacity];
354   EXPECT_GT(cap, 0);
355
356   // Tab is minimum width, both icon and close box should be hidden.
357   NSRect frame = [[controller view] frame];
358   frame.size.width = [TabController minTabWidth];
359   [[controller view] setFrame:frame];
360   EXPECT_FALSE([controller shouldShowIcon]);
361   EXPECT_FALSE([controller shouldShowCloseButton]);
362
363   // Setting the icon when tab is at min width should not show icon (bug 18359).
364   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
365   base::scoped_nsobject<NSImage> image(
366       rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
367   [controller setIconImage:image];
368   NSView* newIcon = [controller iconView];
369   EXPECT_TRUE([newIcon isHidden]);
370
371   // Tab is at active minimum width. Since it's active, the close box
372   // should be visible.
373   [controller setActive:YES];
374   frame = [[controller view] frame];
375   frame.size.width = [TabController minActiveTabWidth];
376   [[controller view] setFrame:frame];
377   EXPECT_FALSE([controller shouldShowIcon]);
378   EXPECT_TRUE([newIcon isHidden]);
379   EXPECT_TRUE([controller shouldShowCloseButton]);
380
381   // Test expanding the tab to max width and ensure the icon and close box
382   // get put back, even when de-activated.
383   frame.size.width = [TabController maxTabWidth];
384   [[controller view] setFrame:frame];
385   EXPECT_TRUE([controller shouldShowIcon]);
386   EXPECT_FALSE([newIcon isHidden]);
387   EXPECT_TRUE([controller shouldShowCloseButton]);
388   [controller setActive:NO];
389   EXPECT_TRUE([controller shouldShowIcon]);
390   EXPECT_TRUE([controller shouldShowCloseButton]);
391
392   cap = [controller iconCapacity];
393   EXPECT_GT(cap, 0);
394 }
395
396 TEST_F(TabControllerTest, Menu) {
397   NSWindow* window = test_window();
398   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
399   base::scoped_nsobject<TabControllerTestTarget> target(
400       [[TabControllerTestTarget alloc] init]);
401   [controller setTarget:target];
402
403   [[window contentView] addSubview:[controller view]];
404   int cap = [controller iconCapacity];
405   EXPECT_GT(cap, 0);
406
407   // Asking the view for its menu should yield a valid menu.
408   NSMenu* menu = [[controller view] menu];
409   EXPECT_TRUE(menu);
410   EXPECT_EQ(3, [menu numberOfItems]);
411 }
412
413 // Tests that the title field is correctly positioned and sized when the
414 // view is resized.
415 TEST_F(TabControllerTest, TitleViewLayout) {
416   NSWindow* window = test_window();
417
418   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
419   [[window contentView] addSubview:[controller view]];
420   NSRect tabFrame = [[controller view] frame];
421   tabFrame.size.width = [TabController maxTabWidth];
422   [[controller view] setFrame:tabFrame];
423
424   const NSRect originalTabFrame = [[controller view] frame];
425   const NSRect originalIconFrame = [[controller iconView] frame];
426   const NSRect originalCloseFrame = [[controller closeButton] frame];
427   const NSRect originalTitleFrame = [[controller tabView] titleFrame];
428
429   // Sanity check the start state.
430   EXPECT_FALSE([[controller iconView] isHidden]);
431   EXPECT_FALSE([[controller closeButton] isHidden]);
432   EXPECT_GT(NSWidth([[controller view] frame]),
433             NSWidth([[controller tabView] titleFrame]));
434
435   // Resize the tab so that that the it shrinks.
436   tabFrame.size.width = [TabController minTabWidth];
437   [[controller view] setFrame:tabFrame];
438
439   // The icon view and close button should be hidden and the title view should
440   // be resize to take up their space.
441   EXPECT_TRUE([[controller iconView] isHidden]);
442   EXPECT_TRUE([[controller closeButton] isHidden]);
443   EXPECT_GT(NSWidth([[controller view] frame]),
444             NSWidth([[controller tabView] titleFrame]));
445   EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame),
446             LeftMargin([[controller view] frame],
447                        [[controller tabView] titleFrame]));
448   EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame),
449             RightMargin([[controller view] frame],
450                         [[controller tabView] titleFrame]));
451
452   // Resize the tab so that that the it grows.
453   tabFrame.size.width = static_cast<int>([TabController maxTabWidth] * 0.75);
454   [[controller view] setFrame:tabFrame];
455
456   // The icon view and close button should be visible again and the title view
457   // should be resized to make room for them.
458   EXPECT_FALSE([[controller iconView] isHidden]);
459   EXPECT_FALSE([[controller closeButton] isHidden]);
460   EXPECT_GT(NSWidth([[controller view] frame]),
461             NSWidth([[controller tabView] titleFrame]));
462   EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame),
463             LeftMargin([[controller view] frame],
464                        [[controller tabView] titleFrame]));
465   EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame),
466             RightMargin([[controller view] frame],
467                         [[controller tabView] titleFrame]));
468 }
469
470 // A comprehensive test of the layout and visibility of all elements (favicon,
471 // throbber indicators, titile text, media indicator button, and close button)
472 // over all relevant combinations of tab state.  This test overlaps with parts
473 // of the other tests above.
474 // Flaky: https://code.google.com/p/chromium/issues/detail?id=311668
475 TEST_F(TabControllerTest, DISABLED_LayoutAndVisibilityOfSubviews) {
476   static const TabMediaState kMediaStatesToTest[] = {
477     TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
478     TAB_MEDIA_STATE_AUDIO_PLAYING, TAB_MEDIA_STATE_AUDIO_MUTING
479   };
480
481   NSWindow* const window = test_window();
482
483   // Create TabController instance and place its view into the test window.
484   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
485   [[window contentView] addSubview:[controller view]];
486
487   // Create favicon.
488   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
489   base::scoped_nsobject<NSImage> favicon(
490       rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
491
492   // Trigger TabController to auto-create the MediaIndicatorButton.
493   [controller setMediaState:TAB_MEDIA_STATE_AUDIO_PLAYING];
494   [controller setMediaState:TAB_MEDIA_STATE_NONE];
495   base::scoped_nsobject<MediaIndicatorButton> mediaIndicatorButton(
496       [[controller mediaIndicatorButton] retain]);
497   ASSERT_TRUE(mediaIndicatorButton.get());
498
499   // Perform layout over all possible combinations, checking for correct
500   // results.
501   for (int isMiniTab = 0; isMiniTab < 2; ++isMiniTab) {
502     for (int isActiveTab = 0; isActiveTab < 2; ++isActiveTab) {
503       for (size_t mediaStateIndex = 0;
504            mediaStateIndex < arraysize(kMediaStatesToTest);
505            ++mediaStateIndex) {
506         const TabMediaState mediaState = kMediaStatesToTest[mediaStateIndex];
507         SCOPED_TRACE(::testing::Message()
508                      << (isActiveTab ? "Active" : "Inactive") << ' '
509                      << (isMiniTab ? "Mini " : "")
510                      << "Tab with media indicator state " << mediaState);
511
512         // Simulate what tab_strip_controller would do to set up the
513         // TabController state.
514         [controller setMini:(isMiniTab ? YES : NO)];
515         [controller setActive:(isActiveTab ? YES : NO)];
516         [controller setIconImage:favicon];
517         [controller setMediaState:mediaState];
518         [controller updateVisibility];
519
520         // Test layout for every width from maximum to minimum.
521         NSRect tabFrame = [[controller view] frame];
522         int minWidth;
523         if (isMiniTab) {
524           tabFrame.size.width = minWidth = [TabController miniTabWidth];
525         } else {
526           tabFrame.size.width = [TabController maxTabWidth];
527           minWidth = isActiveTab ? [TabController minActiveTabWidth] :
528               [TabController minTabWidth];
529         }
530         while (NSWidth(tabFrame) >= minWidth) {
531           SCOPED_TRACE(::testing::Message() << "width=" << tabFrame.size.width);
532           [[controller view] setFrame:tabFrame];
533           CheckForExpectedLayoutAndVisibilityOfSubviews(controller);
534           --tabFrame.size.width;
535         }
536       }
537     }
538   }
539 }
540
541 }  // namespace