- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / tabs / tab_strip_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 <Cocoa/Cocoa.h>
6
7 #include "base/bind_helpers.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
10 #include "chrome/browser/media/media_stream_capture_indicator.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
13 #import "chrome/browser/ui/cocoa/new_tab_button.h"
14 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
15 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
16 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
17 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
18 #include "chrome/browser/ui/tabs/tab_utils.h"
19 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
20 #include "chrome/test/base/testing_profile.h"
21 #include "content/public/browser/site_instance.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/common/media_stream_request.h"
24 #import "testing/gtest_mac.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "testing/platform_test.h"
27 #include "ui/base/test/cocoa_test_event_utils.h"
28
29 using content::SiteInstance;
30 using content::WebContents;
31
32 @interface TestTabStripControllerDelegate
33     : NSObject<TabStripControllerDelegate> {
34 }
35 @end
36
37 @implementation TestTabStripControllerDelegate
38 - (void)onActivateTabWithContents:(WebContents*)contents {
39 }
40 - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
41         withContents:(WebContents*)contents {
42 }
43 - (void)onTabDetachedWithContents:(WebContents*)contents {
44 }
45 @end
46
47
48 // Helper class for invoking a base::Closure via
49 // -performSelector:withObject:afterDelay:.
50 @interface TestClosureRunner : NSObject {
51  @private
52   base::Closure closure_;
53 }
54 - (id)initWithClosure:(const base::Closure&)closure;
55 - (void)scheduleDelayedRun;
56 - (void)run;
57 @end
58
59 @implementation TestClosureRunner
60 - (id)initWithClosure:(const base::Closure&)closure {
61   if (self) {
62     closure_ = closure;
63   }
64   return self;
65 }
66 - (void)scheduleDelayedRun {
67   [self performSelector:@selector(run) withObject:nil afterDelay:0];
68 }
69 - (void)run {
70   closure_.Run();
71 }
72 @end
73
74 @interface TabStripController (Test)
75
76 - (void)mouseMoved:(NSEvent*)event;
77
78 @end
79
80 @implementation TabView (Test)
81
82 - (TabController*)controller {
83   return controller_;
84 }
85
86 @end
87
88 namespace {
89
90 class TabStripControllerTest : public CocoaProfileTest {
91  public:
92   virtual void SetUp() OVERRIDE {
93     CocoaProfileTest::SetUp();
94     ASSERT_TRUE(browser());
95
96     NSWindow* window = browser()->window()->GetNativeWindow();
97     NSView* parent = [window contentView];
98     NSRect content_frame = [parent frame];
99
100     // Create the "switch view" (view that gets changed out when a tab
101     // switches).
102     NSRect switch_frame = NSMakeRect(0, 0, content_frame.size.width, 500);
103     base::scoped_nsobject<NSView> switch_view(
104         [[NSView alloc] initWithFrame:switch_frame]);
105     [parent addSubview:switch_view.get()];
106
107     // Create the tab strip view. It's expected to have a child button in it
108     // already as the "new tab" button so create that too.
109     NSRect strip_frame = NSMakeRect(0, NSMaxY(switch_frame),
110                                     content_frame.size.width, 30);
111     tab_strip_.reset(
112         [[TabStripView alloc] initWithFrame:strip_frame]);
113     [parent addSubview:tab_strip_.get()];
114     NSRect button_frame = NSMakeRect(0, 0, 15, 15);
115     base::scoped_nsobject<NewTabButton> new_tab_button(
116         [[NewTabButton alloc] initWithFrame:button_frame]);
117     [tab_strip_ addSubview:new_tab_button.get()];
118     [tab_strip_ setNewTabButton:new_tab_button.get()];
119
120     delegate_.reset(new TestTabStripModelDelegate());
121     model_ = browser()->tab_strip_model();
122     controller_delegate_.reset([TestTabStripControllerDelegate alloc]);
123     controller_.reset([[TabStripController alloc]
124                       initWithView:static_cast<TabStripView*>(tab_strip_.get())
125                         switchView:switch_view.get()
126                            browser:browser()
127                           delegate:controller_delegate_.get()]);
128   }
129
130   virtual void TearDown() OVERRIDE {
131     // The call to CocoaTest::TearDown() deletes the Browser and TabStripModel
132     // objects, so we first have to delete the controller, which refers to them.
133     controller_.reset();
134     model_ = NULL;
135     CocoaProfileTest::TearDown();
136   }
137
138   TabView* CreateTab() {
139     SiteInstance* instance = SiteInstance::Create(profile());
140     WebContents* web_contents = WebContents::Create(
141         content::WebContents::CreateParams(profile(), instance));
142     model_->AppendWebContents(web_contents, true);
143     const NSUInteger tab_count = [controller_.get() viewsCount];
144     return static_cast<TabView*>([controller_.get() viewAtIndex:tab_count - 1]);
145   }
146
147   // Closes all tabs and unrefs the tabstrip and then posts a NSLeftMouseUp
148   // event which should end the nested drag event loop.
149   void CloseTabsAndEndDrag() {
150     // Simulate a close of the browser window.
151     model_->CloseAllTabs();
152     controller_.reset();
153     tab_strip_.reset();
154     // Schedule a NSLeftMouseUp to end the nested drag event loop.
155     NSEvent* event =
156         cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0);
157     [NSApp postEvent:event atStart:NO];
158   }
159
160   scoped_ptr<TestTabStripModelDelegate> delegate_;
161   TabStripModel* model_;
162   base::scoped_nsobject<TestTabStripControllerDelegate> controller_delegate_;
163   base::scoped_nsobject<TabStripController> controller_;
164   base::scoped_nsobject<TabStripView> tab_strip_;
165 };
166
167 // Test adding and removing tabs and making sure that views get added to
168 // the tab strip.
169 TEST_F(TabStripControllerTest, AddRemoveTabs) {
170   EXPECT_TRUE(model_->empty());
171   CreateTab();
172   EXPECT_EQ(model_->count(), 1);
173 }
174
175 TEST_F(TabStripControllerTest, SelectTab) {
176   // TODO(pinkerton): Implement http://crbug.com/10899
177 }
178
179 TEST_F(TabStripControllerTest, RearrangeTabs) {
180   // TODO(pinkerton): Implement http://crbug.com/10899
181 }
182
183 TEST_F(TabStripControllerTest, CorrectToolTipMouseHoverBehavior) {
184   // Set tab 1 tooltip.
185   TabView* tab1 = CreateTab();
186   [tab1 setToolTip:@"Tab1"];
187
188   // Set tab 2 tooltip.
189   TabView* tab2 = CreateTab();
190   [tab2 setToolTip:@"Tab2"];
191
192   EXPECT_FALSE([tab1 controller].selected);
193   EXPECT_TRUE([tab2 controller].selected);
194
195   // Check that there's no tooltip yet.
196   EXPECT_FALSE([controller_ view:nil
197                 stringForToolTip:nil
198                            point:NSZeroPoint
199                         userData:nil]);
200
201   // Set up mouse event on overlap of tab1 + tab2.
202   const CGFloat min_y = NSMinY([tab_strip_.get() frame]) + 1;
203
204   // Hover over overlap between tab 1 and 2.
205   NSEvent* event =
206       cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(280, min_y),
207                                                 NSMouseMoved, 0);
208   [controller_.get() mouseMoved:event];
209   EXPECT_STREQ("Tab2",
210       [[controller_ view:nil
211         stringForToolTip:nil
212                    point:NSZeroPoint
213                 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]);
214
215
216   // Hover over tab 1.
217   event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(260, min_y),
218                                                     NSMouseMoved, 0);
219   [controller_.get() mouseMoved:event];
220   EXPECT_STREQ("Tab1",
221       [[controller_ view:nil
222         stringForToolTip:nil
223                    point:NSZeroPoint
224                 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]);
225
226   // Hover over tab 2.
227   event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(290, min_y),
228                                                     NSMouseMoved, 0);
229   [controller_.get() mouseMoved:event];
230   EXPECT_STREQ("Tab2",
231       [[controller_ view:nil
232         stringForToolTip:nil
233                    point:NSZeroPoint
234                 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]);
235 }
236
237 TEST_F(TabStripControllerTest, CorrectTitleAndToolTipTextFromSetTabTitle) {
238   using content::MediaStreamDevice;
239   using content::MediaStreamDevices;
240   using content::MediaStreamUI;
241
242   TabView* const tab = CreateTab();
243   TabController* const tabController = [tab controller];
244   WebContents* const contents = model_->GetActiveWebContents();
245
246   // Initially, tab title and tooltip text are equivalent.
247   EXPECT_EQ(TAB_MEDIA_STATE_NONE,
248             chrome::GetTabMediaStateForContents(contents));
249   [controller_ setTabTitle:tabController withContents:contents];
250   NSString* const baseTitle = [tabController title];
251   EXPECT_NSEQ(baseTitle, [tabController toolTip]);
252
253   // Simulate the start of tab video capture.  Tab title remains the same, but
254   // the tooltip text should include the following appended: 1) a line break;
255   // 2) a non-empty string with a localized description of the media state.
256   scoped_refptr<MediaStreamCaptureIndicator> indicator =
257       MediaCaptureDevicesDispatcher::GetInstance()->
258           GetMediaStreamCaptureIndicator();
259   const MediaStreamDevice dummyVideoCaptureDevice(
260       content::MEDIA_TAB_VIDEO_CAPTURE, "dummy_id", "dummy name");
261   scoped_ptr<MediaStreamUI> streamUi(indicator->RegisterMediaStream(
262       contents, MediaStreamDevices(1, dummyVideoCaptureDevice)));
263   streamUi->OnStarted(base::Bind(&base::DoNothing));
264   EXPECT_EQ(TAB_MEDIA_STATE_CAPTURING,
265             chrome::GetTabMediaStateForContents(contents));
266   [controller_ setTabTitle:tabController withContents:contents];
267   EXPECT_NSEQ(baseTitle, [tabController title]);
268   NSString* const toolTipText = [tabController toolTip];
269   if ([baseTitle length] > 0) {
270     EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, [baseTitle length]),
271                               [toolTipText rangeOfString:baseTitle]));
272     EXPECT_TRUE(NSEqualRanges(NSMakeRange([baseTitle length], 1),
273                               [toolTipText rangeOfString:@"\n"]));
274     EXPECT_LT([baseTitle length] + 1, [toolTipText length]);
275   } else {
276     EXPECT_LT(0u, [toolTipText length]);
277   }
278
279   // Simulate the end of tab video capture.  Tab title and tooltip should become
280   // equivalent again.
281   streamUi.reset();
282   EXPECT_EQ(TAB_MEDIA_STATE_NONE,
283             chrome::GetTabMediaStateForContents(contents));
284   [controller_ setTabTitle:tabController withContents:contents];
285   EXPECT_NSEQ(baseTitle, [tabController title]);
286   EXPECT_NSEQ(baseTitle, [tabController toolTip]);
287 }
288
289 TEST_F(TabStripControllerTest, TabCloseDuringDrag) {
290   TabController* tab;
291   // The TabController gets autoreleased when created, but is owned by the
292   // tab strip model. Use a ScopedNSAutoreleasePool to get a truly weak ref
293   // to it to test that -maybeStartDrag:forTab: can handle that properly.
294   {
295     base::mac::ScopedNSAutoreleasePool pool;
296     tab = [CreateTab() controller];
297   }
298
299   // Schedule a task to close all the tabs and stop the drag, before the call to
300   // -maybeStartDrag:forTab:, which starts a nested event loop. This task will
301   // run in that nested event loop, which shouldn't crash.
302   base::scoped_nsobject<TestClosureRunner> runner([[TestClosureRunner alloc]
303       initWithClosure:base::Bind(&TabStripControllerTest::CloseTabsAndEndDrag,
304                                  base::Unretained(this))]);
305   [runner scheduleDelayedRun];
306
307   NSEvent* event =
308       cocoa_test_event_utils::LeftMouseDownAtPoint(NSZeroPoint);
309   [[controller_ dragController] maybeStartDrag:event forTab:tab];
310 }
311
312 TEST_F(TabStripControllerTest, ViewAccessibility_Contents) {
313   NSArray* attrs = [tab_strip_ accessibilityAttributeNames];
314   ASSERT_TRUE([attrs containsObject:NSAccessibilityContentsAttribute]);
315
316   // Create two tabs and ensure they exist in the contents array.
317   TabView* tab1 = CreateTab();
318   TabView* tab2 = CreateTab();
319   NSObject* contents =
320       [tab_strip_ accessibilityAttributeValue:NSAccessibilityContentsAttribute];
321   DCHECK([contents isKindOfClass:[NSArray class]]);
322   NSArray* contentsArray = static_cast<NSArray*>(contents);
323   ASSERT_TRUE([contentsArray containsObject:tab1]);
324   ASSERT_TRUE([contentsArray containsObject:tab2]);
325 }
326
327 TEST_F(TabStripControllerTest, ViewAccessibility_Value) {
328   NSArray* attrs = [tab_strip_ accessibilityAttributeNames];
329   ASSERT_TRUE([attrs containsObject:NSAccessibilityValueAttribute]);
330
331   // Create two tabs and ensure the active one gets returned.
332   TabView* tab1 = CreateTab();
333   TabView* tab2 = CreateTab();
334   EXPECT_FALSE([tab1 controller].selected);
335   EXPECT_TRUE([tab2 controller].selected);
336   NSObject* value =
337       [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute];
338   EXPECT_EQ(tab2, value);
339
340   model_->ActivateTabAt(0, false);
341   EXPECT_TRUE([tab1 controller].selected);
342   EXPECT_FALSE([tab2 controller].selected);
343   value =
344       [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute];
345   EXPECT_EQ(tab1, value);
346 }
347
348 }  // namespace