- add sources.
[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_view.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 #include "grit/theme_resources.h"
15 #include "grit/ui_resources.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #import "testing/gtest_mac.h"
18 #include "testing/platform_test.h"
19 #include "ui/base/resource/resource_bundle.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, ASCIIToUTF16("Hello World"));
79   model->AddItem(2, ASCIIToUTF16("Allays"));
80   model->AddItem(3, 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 // Helper to create an NSImageView that contains an image fetched from
99 // ui::ResourceBundle.
100 NSImageView* CreateImageViewFromResourceBundle(int resource_id) {
101   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
102   NSImage* const image = rb.GetNativeImageNamed(resource_id).ToNSImage();
103   CHECK(!!image);
104   NSRect frame;
105   frame.size = [image size];
106   NSImageView* const view = [[NSImageView alloc] initWithFrame:frame];
107   [view setImage:image];
108   return view;
109 }
110
111 // The dragging code in TabView makes heavy use of autorelease pools so
112 // inherit from CocoaTest to have one created for us.
113 class TabControllerTest : public CocoaTest {
114  public:
115   TabControllerTest() { }
116
117   static void CheckForExpectedLayoutAndVisibilityOfSubviews(
118       const TabController* controller) {
119     // Check whether subviews should be visible when they are supposed to be,
120     // given Tab size and TabRendererData state.
121     const TabMediaState indicatorState =
122         [[controller mediaIndicatorView] mediaState];
123     if ([controller mini]) {
124       EXPECT_EQ(1, [controller iconCapacity]);
125       if (indicatorState != TAB_MEDIA_STATE_NONE) {
126         EXPECT_FALSE([controller shouldShowIcon]);
127         EXPECT_TRUE([controller shouldShowMediaIndicator]);
128       } else {
129         EXPECT_TRUE([controller shouldShowIcon]);
130         EXPECT_FALSE([controller shouldShowMediaIndicator]);
131       }
132       EXPECT_FALSE([controller shouldShowCloseButton]);
133     } else if ([controller selected]) {
134       EXPECT_TRUE([controller shouldShowCloseButton]);
135       switch ([controller iconCapacity]) {
136         case 0:
137         case 1:
138           EXPECT_FALSE([controller shouldShowIcon]);
139           EXPECT_FALSE([controller shouldShowMediaIndicator]);
140           break;
141         case 2:
142           if (indicatorState != TAB_MEDIA_STATE_NONE) {
143             EXPECT_FALSE([controller shouldShowIcon]);
144             EXPECT_TRUE([controller shouldShowMediaIndicator]);
145           } else {
146             EXPECT_TRUE([controller shouldShowIcon]);
147             EXPECT_FALSE([controller shouldShowMediaIndicator]);
148           }
149           break;
150         default:
151           EXPECT_LE(3, [controller iconCapacity]);
152           EXPECT_TRUE([controller shouldShowIcon]);
153           if (indicatorState != TAB_MEDIA_STATE_NONE)
154             EXPECT_TRUE([controller shouldShowMediaIndicator]);
155           else
156             EXPECT_FALSE([controller shouldShowMediaIndicator]);
157           break;
158       }
159     } else {  // Tab not selected/active and not mini tab.
160       switch ([controller iconCapacity]) {
161         case 0:
162           EXPECT_FALSE([controller shouldShowCloseButton]);
163           EXPECT_FALSE([controller shouldShowIcon]);
164           EXPECT_FALSE([controller shouldShowMediaIndicator]);
165           break;
166         case 1:
167           EXPECT_FALSE([controller shouldShowCloseButton]);
168           if (indicatorState != TAB_MEDIA_STATE_NONE) {
169             EXPECT_FALSE([controller shouldShowIcon]);
170             EXPECT_TRUE([controller shouldShowMediaIndicator]);
171           } else {
172             EXPECT_TRUE([controller shouldShowIcon]);
173             EXPECT_FALSE([controller shouldShowMediaIndicator]);
174           }
175           break;
176         default:
177           EXPECT_LE(2, [controller iconCapacity]);
178           EXPECT_TRUE([controller shouldShowIcon]);
179           if (indicatorState != TAB_MEDIA_STATE_NONE)
180             EXPECT_TRUE([controller shouldShowMediaIndicator]);
181           else
182             EXPECT_FALSE([controller shouldShowMediaIndicator]);
183           break;
184       }
185     }
186
187     // Make sure the NSView's "isHidden" state jives with the "shouldShowXXX."
188     EXPECT_TRUE([controller shouldShowIcon] ==
189                 (!![controller iconView] && ![[controller iconView] isHidden]));
190     EXPECT_TRUE([controller mini] == [[controller titleView] isHidden]);
191     EXPECT_TRUE([controller shouldShowMediaIndicator] ==
192                     ![[controller mediaIndicatorView] isHidden]);
193     EXPECT_TRUE([controller shouldShowCloseButton] !=
194                     [[controller closeButton] isHidden]);
195
196     // Check positioning of elements with respect to each other, and that they
197     // are fully within the tab frame.
198     const NSRect tabFrame = [[controller view] frame];
199     const NSRect titleFrame = [[controller titleView] frame];
200     if ([controller shouldShowIcon]) {
201       const NSRect iconFrame = [[controller iconView] frame];
202       EXPECT_LE(NSMinX(tabFrame), NSMinX(iconFrame));
203       if (NSWidth(titleFrame) > 0)
204         EXPECT_LE(NSMaxX(iconFrame), NSMinX(titleFrame));
205       EXPECT_LE(NSMinY(tabFrame), NSMinY(iconFrame));
206       EXPECT_LE(NSMaxY(iconFrame), NSMaxY(tabFrame));
207     }
208     if ([controller shouldShowIcon] && [controller shouldShowMediaIndicator]) {
209       EXPECT_LE(NSMaxX([[controller iconView] frame]),
210                 NSMinX([[controller mediaIndicatorView] frame]));
211     }
212     if ([controller shouldShowMediaIndicator]) {
213       const NSRect mediaIndicatorFrame =
214           [[controller mediaIndicatorView] frame];
215       if (NSWidth(titleFrame) > 0)
216         EXPECT_LE(NSMaxX(titleFrame), NSMinX(mediaIndicatorFrame));
217       EXPECT_LE(NSMaxX(mediaIndicatorFrame), NSMaxX(tabFrame));
218       EXPECT_LE(NSMinY(tabFrame), NSMinY(mediaIndicatorFrame));
219       EXPECT_LE(NSMaxY(mediaIndicatorFrame), NSMaxY(tabFrame));
220     }
221     if ([controller shouldShowMediaIndicator] &&
222         [controller shouldShowCloseButton]) {
223       EXPECT_LE(NSMaxX([[controller mediaIndicatorView] frame]),
224                 NSMinX([[controller closeButton] frame]));
225     }
226     if ([controller shouldShowCloseButton]) {
227       const NSRect closeButtonFrame = [[controller closeButton] frame];
228       if (NSWidth(titleFrame) > 0)
229         EXPECT_LE(NSMaxX(titleFrame), NSMinX(closeButtonFrame));
230       EXPECT_LE(NSMaxX(closeButtonFrame), NSMaxX(tabFrame));
231       EXPECT_LE(NSMinY(tabFrame), NSMinY(closeButtonFrame));
232       EXPECT_LE(NSMaxY(closeButtonFrame), NSMaxY(tabFrame));
233     }
234   }
235 };
236
237 // Tests creating the controller, sticking it in a window, and removing it.
238 TEST_F(TabControllerTest, Creation) {
239   NSWindow* window = test_window();
240   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
241   [[window contentView] addSubview:[controller view]];
242   EXPECT_TRUE([controller tabView]);
243   EXPECT_EQ([[controller view] window], window);
244   [[controller view] display];  // Test drawing to ensure nothing leaks/crashes.
245   [[controller view] removeFromSuperview];
246 }
247
248 // Tests sending it a close message and ensuring that the target/action get
249 // called. Mimics the user clicking on the close button in the tab.
250 TEST_F(TabControllerTest, Close) {
251   NSWindow* window = test_window();
252   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
253   [[window contentView] addSubview:[controller view]];
254
255   base::scoped_nsobject<TabControllerTestTarget> target(
256       [[TabControllerTestTarget alloc] init]);
257   EXPECT_FALSE([target closed]);
258   [controller setTarget:target];
259   EXPECT_EQ(target.get(), [controller target]);
260
261   [controller closeTab:nil];
262   EXPECT_TRUE([target closed]);
263
264   [[controller view] removeFromSuperview];
265 }
266
267 // Tests setting the |selected| property via code.
268 TEST_F(TabControllerTest, APISelection) {
269   NSWindow* window = test_window();
270   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
271   [[window contentView] addSubview:[controller view]];
272
273   EXPECT_FALSE([controller selected]);
274   [controller setSelected:YES];
275   EXPECT_TRUE([controller selected]);
276
277   [[controller view] removeFromSuperview];
278 }
279
280 // Tests setting the |loading| property via code.
281 TEST_F(TabControllerTest, Loading) {
282   NSWindow* window = test_window();
283   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
284   [[window contentView] addSubview:[controller view]];
285
286   EXPECT_EQ(kTabDone, [controller loadingState]);
287   [controller setLoadingState:kTabWaiting];
288   EXPECT_EQ(kTabWaiting, [controller loadingState]);
289   [controller setLoadingState:kTabLoading];
290   EXPECT_EQ(kTabLoading, [controller loadingState]);
291   [controller setLoadingState:kTabDone];
292   EXPECT_EQ(kTabDone, [controller loadingState]);
293
294   [[controller view] removeFromSuperview];
295 }
296
297 // Tests selecting the tab with the mouse click and ensuring the target/action
298 // get called.
299 TEST_F(TabControllerTest, UserSelection) {
300   NSWindow* window = test_window();
301
302   // Create a tab at a known location in the window that we can click on
303   // to activate selection.
304   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
305   [[window contentView] addSubview:[controller view]];
306   NSRect frame = [[controller view] frame];
307   frame.size.width = [TabController minTabWidth];
308   frame.origin = NSZeroPoint;
309   [[controller view] setFrame:frame];
310
311   // Set the target and action.
312   base::scoped_nsobject<TabControllerTestTarget> target(
313       [[TabControllerTestTarget alloc] init]);
314   EXPECT_FALSE([target selected]);
315   [controller setTarget:target];
316   [controller setAction:@selector(selectTab:)];
317   EXPECT_EQ(target.get(), [controller target]);
318   EXPECT_EQ(@selector(selectTab:), [controller action]);
319
320   // In order to track a click, we have to fake a mouse down and a mouse
321   // up, but the down goes into a tight drag loop. To break the loop, we have
322   // to fire a timer that sends a mouse up event while the "drag" is ongoing.
323   [NSTimer scheduledTimerWithTimeInterval:0.1
324                                    target:target.get()
325                                  selector:@selector(mouseTimer:)
326                                  userInfo:window
327                                   repeats:NO];
328   NSEvent* current = [NSApp currentEvent];
329   NSPoint click_point = NSMakePoint(frame.size.width / 2,
330                                     frame.size.height / 2);
331   NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown
332                                      location:click_point
333                                 modifierFlags:0
334                                     timestamp:[current timestamp]
335                                  windowNumber:[window windowNumber]
336                                       context:nil
337                                   eventNumber:0
338                                    clickCount:1
339                                      pressure:1.0];
340   [[controller view] mouseDown:down];
341
342   // Check our target was told the tab got selected.
343   EXPECT_TRUE([target selected]);
344
345   [[controller view] removeFromSuperview];
346 }
347
348 TEST_F(TabControllerTest, IconCapacity) {
349   NSWindow* window = test_window();
350   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
351   [[window contentView] addSubview:[controller view]];
352   int cap = [controller iconCapacity];
353   EXPECT_GE(cap, 1);
354
355   NSRect frame = [[controller view] frame];
356   frame.size.width += 500;
357   [[controller view] setFrame:frame];
358   int newcap = [controller iconCapacity];
359   EXPECT_GT(newcap, cap);
360 }
361
362 TEST_F(TabControllerTest, ShouldShowIcon) {
363   NSWindow* window = test_window();
364   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
365   [[window contentView] addSubview:[controller view]];
366   int cap = [controller iconCapacity];
367   EXPECT_GT(cap, 0);
368
369   // Tab is minimum width, both icon and close box should be hidden.
370   NSRect frame = [[controller view] frame];
371   frame.size.width = [TabController minTabWidth];
372   [[controller view] setFrame:frame];
373   EXPECT_FALSE([controller shouldShowIcon]);
374   EXPECT_FALSE([controller shouldShowCloseButton]);
375
376   // Setting the icon when tab is at min width should not show icon (bug 18359).
377   base::scoped_nsobject<NSView> newIcon(
378       [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 16, 16)]);
379   [controller setIconView:newIcon.get()];
380   EXPECT_TRUE([newIcon isHidden]);
381
382   // Tab is at selected minimum width. Since it's selected, the close box
383   // should be visible.
384   [controller setSelected:YES];
385   frame = [[controller view] frame];
386   frame.size.width = [TabController minSelectedTabWidth];
387   [[controller view] setFrame:frame];
388   EXPECT_FALSE([controller shouldShowIcon]);
389   EXPECT_TRUE([newIcon isHidden]);
390   EXPECT_TRUE([controller shouldShowCloseButton]);
391
392   // Test expanding the tab to max width and ensure the icon and close box
393   // get put back, even when de-selected.
394   frame.size.width = [TabController maxTabWidth];
395   [[controller view] setFrame:frame];
396   EXPECT_TRUE([controller shouldShowIcon]);
397   EXPECT_FALSE([newIcon isHidden]);
398   EXPECT_TRUE([controller shouldShowCloseButton]);
399   [controller setSelected:NO];
400   EXPECT_TRUE([controller shouldShowIcon]);
401   EXPECT_TRUE([controller shouldShowCloseButton]);
402
403   cap = [controller iconCapacity];
404   EXPECT_GT(cap, 0);
405 }
406
407 TEST_F(TabControllerTest, Menu) {
408   NSWindow* window = test_window();
409   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
410   base::scoped_nsobject<TabControllerTestTarget> target(
411       [[TabControllerTestTarget alloc] init]);
412   [controller setTarget:target];
413
414   [[window contentView] addSubview:[controller view]];
415   int cap = [controller iconCapacity];
416   EXPECT_GT(cap, 0);
417
418   // Asking the view for its menu should yield a valid menu.
419   NSMenu* menu = [[controller view] menu];
420   EXPECT_TRUE(menu);
421   EXPECT_EQ(3, [menu numberOfItems]);
422 }
423
424 // Tests that the title field is correctly positioned and sized when the
425 // view is resized.
426 TEST_F(TabControllerTest, TitleViewLayout) {
427   NSWindow* window = test_window();
428
429   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
430   [[window contentView] addSubview:[controller view]];
431   NSRect tabFrame = [[controller view] frame];
432   tabFrame.size.width = [TabController maxTabWidth];
433   [[controller view] setFrame:tabFrame];
434
435   const NSRect originalTabFrame = [[controller view] frame];
436   const NSRect originalIconFrame = [[controller iconView] frame];
437   const NSRect originalCloseFrame = [[controller closeButton] frame];
438   const NSRect originalTitleFrame = [[controller titleView] frame];
439
440   // Sanity check the start state.
441   EXPECT_FALSE([[controller iconView] isHidden]);
442   EXPECT_FALSE([[controller closeButton] isHidden]);
443   EXPECT_GT(NSWidth([[controller view] frame]),
444             NSWidth([[controller titleView] frame]));
445
446   // Resize the tab so that that the it shrinks.
447   tabFrame.size.width = [TabController minTabWidth];
448   [[controller view] setFrame:tabFrame];
449
450   // The icon view and close button should be hidden and the title view should
451   // be resize to take up their space.
452   EXPECT_TRUE([[controller iconView] isHidden]);
453   EXPECT_TRUE([[controller closeButton] isHidden]);
454   EXPECT_GT(NSWidth([[controller view] frame]),
455             NSWidth([[controller titleView] frame]));
456   EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame),
457             LeftMargin([[controller view] frame],
458                        [[controller titleView] frame]));
459   EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame),
460             RightMargin([[controller view] frame],
461                         [[controller titleView] frame]));
462
463   // Resize the tab so that that the it grows.
464   tabFrame.size.width = static_cast<int>([TabController maxTabWidth] * 0.75);
465   [[controller view] setFrame:tabFrame];
466
467   // The icon view and close button should be visible again and the title view
468   // should be resized to make room for them.
469   EXPECT_FALSE([[controller iconView] isHidden]);
470   EXPECT_FALSE([[controller closeButton] isHidden]);
471   EXPECT_GT(NSWidth([[controller view] frame]),
472             NSWidth([[controller titleView] frame]));
473   EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame),
474             LeftMargin([[controller view] frame],
475                        [[controller titleView] frame]));
476   EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame),
477             RightMargin([[controller view] frame],
478                         [[controller titleView] frame]));
479 }
480
481 // A comprehensive test of the layout and visibility of all elements (favicon,
482 // throbber indicators, titile text, audio indicator, and close button) over all
483 // relevant combinations of tab state.  This test overlaps with parts of the
484 // other tests above.
485 // Flaky: https://code.google.com/p/chromium/issues/detail?id=311668
486 TEST_F(TabControllerTest, DISABLED_LayoutAndVisibilityOfSubviews) {
487   static const TabMediaState kMediaStatesToTest[] = {
488     TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
489     TAB_MEDIA_STATE_AUDIO_PLAYING
490   };
491
492   NSWindow* const window = test_window();
493
494   // Create TabController instance and place its view into the test window.
495   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
496   [[window contentView] addSubview:[controller view]];
497
498   // Create favicon and media indicator views.  Disable animation in the media
499   // indicator view so that TabController's "what should be shown" logic can be
500   // tested effectively.  If animations were left enabled, the
501   // shouldShowMediaIndicator method would return true during fade-out
502   // transitions.
503   base::scoped_nsobject<NSImageView> faviconView(
504       CreateImageViewFromResourceBundle(IDR_DEFAULT_FAVICON));
505   base::scoped_nsobject<MediaIndicatorView> mediaIndicatorView(
506       [[MediaIndicatorView alloc] init]);
507   [mediaIndicatorView disableAnimations];
508   [controller setMediaIndicatorView:mediaIndicatorView];
509
510   // Perform layout over all possible combinations, checking for correct
511   // results.
512   for (int isMiniTab = 0; isMiniTab < 2; ++isMiniTab) {
513     for (int isActiveTab = 0; isActiveTab < 2; ++isActiveTab) {
514       for (size_t mediaStateIndex = 0;
515            mediaStateIndex < arraysize(kMediaStatesToTest);
516            ++mediaStateIndex) {
517         const TabMediaState mediaState = kMediaStatesToTest[mediaStateIndex];
518         SCOPED_TRACE(::testing::Message()
519                      << (isActiveTab ? "Active" : "Inactive") << ' '
520                      << (isMiniTab ? "Mini " : "")
521                      << "Tab with media indicator state " << mediaState);
522
523         // Simulate what tab_strip_controller would do to set up the
524         // TabController state.
525         [controller setMini:(isMiniTab ? YES : NO)];
526         [controller setActive:(isActiveTab ? YES : NO)];
527         [[controller mediaIndicatorView] updateIndicator:mediaState];
528         [controller setIconView:faviconView];
529
530         // Test layout for every width from maximum to minimum.
531         NSRect tabFrame = [[controller view] frame];
532         int minWidth;
533         if (isMiniTab) {
534           tabFrame.size.width = minWidth = [TabController miniTabWidth];
535         } else {
536           tabFrame.size.width = [TabController maxTabWidth];
537           minWidth = isActiveTab ? [TabController minSelectedTabWidth] :
538               [TabController minTabWidth];
539         }
540         while (NSWidth(tabFrame) >= minWidth) {
541           SCOPED_TRACE(::testing::Message() << "width=" << tabFrame.size.width);
542           [[controller view] setFrame:tabFrame];
543           CheckForExpectedLayoutAndVisibilityOfSubviews(controller);
544           --tabFrame.size.width;
545         }
546       }
547     }
548   }
549 }
550
551 }  // namespace