Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / tabs / tab_strip_controller_unittest.mm
blobcfe3a71bb9e0a5e9f3eabb76901cd39a4506f421
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 <Cocoa/Cocoa.h>
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 #include "testing/gtest/include/gtest/gtest.h"
25 #import "testing/gtest_mac.h"
26 #include "testing/platform_test.h"
27 #include "ui/events/test/cocoa_test_event_utils.h"
29 using content::SiteInstance;
30 using content::WebContents;
32 @interface TestTabStripControllerDelegate
33     : NSObject<TabStripControllerDelegate> {
35 @end
37 @implementation TestTabStripControllerDelegate
38 - (void)onActivateTabWithContents:(WebContents*)contents {
40 - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
41         withContents:(WebContents*)contents {
43 - (void)onTabDetachedWithContents:(WebContents*)contents {
45 @end
48 // Helper class for invoking a base::Closure via
49 // -performSelector:withObject:afterDelay:.
50 @interface TestClosureRunner : NSObject {
51  @private
52   base::Closure closure_;
54 - (id)initWithClosure:(const base::Closure&)closure;
55 - (void)scheduleDelayedRun;
56 - (void)run;
57 @end
59 @implementation TestClosureRunner
60 - (id)initWithClosure:(const base::Closure&)closure {
61   if (self) {
62     closure_ = closure;
63   }
64   return self;
66 - (void)scheduleDelayedRun {
67   [self performSelector:@selector(run) withObject:nil afterDelay:0];
69 - (void)run {
70   closure_.Run();
72 @end
74 @interface TabStripController (Test)
76 - (void)mouseMoved:(NSEvent*)event;
78 @end
80 @implementation TabView (Test)
82 - (TabController*)controller {
83   return controller_;
86 @end
88 namespace {
90 class TabStripControllerTest : public CocoaProfileTest {
91  public:
92   void SetUp() override {
93     CocoaProfileTest::SetUp();
94     ASSERT_TRUE(browser());
96     NSWindow* window = browser()->window()->GetNativeWindow();
97     NSView* parent = [window contentView];
98     NSRect content_frame = [parent frame];
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()];
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()];
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   }
130   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   }
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   }
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   }
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_;
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);
175 // Clicking a selected (but inactive) tab should activate it.
176 TEST_F(TabStripControllerTest, ActivateSelectedButInactiveTab) {
177   TabView* tab0 = CreateTab();
178   TabView* tab1 = CreateTab();
179   model_->ToggleSelectionAt(0);
180   EXPECT_TRUE([[tab0 controller] selected]);
181   EXPECT_TRUE([[tab1 controller] selected]);
183   [controller_ selectTab:tab1];
184   EXPECT_EQ(1, model_->active_index());
187 // Toggling (cmd-click) a selected (but inactive) tab should deselect it.
188 TEST_F(TabStripControllerTest, DeselectInactiveTab) {
189   TabView* tab0 = CreateTab();
190   TabView* tab1 = CreateTab();
191   model_->ToggleSelectionAt(0);
192   EXPECT_TRUE([[tab0 controller] selected]);
193   EXPECT_TRUE([[tab1 controller] selected]);
195   model_->ToggleSelectionAt(1);
196   EXPECT_TRUE([[tab0 controller] selected]);
197   EXPECT_FALSE([[tab1 controller] selected]);
200 TEST_F(TabStripControllerTest, SelectTab) {
201   // TODO(pinkerton): Implement http://crbug.com/10899
204 TEST_F(TabStripControllerTest, RearrangeTabs) {
205   // TODO(pinkerton): Implement http://crbug.com/10899
208 TEST_F(TabStripControllerTest, CorrectMouseHoverBehavior) {
209   TabView* tab1 = CreateTab();
210   TabView* tab2 = CreateTab();
212   EXPECT_FALSE([tab1 controller].selected);
213   EXPECT_TRUE([tab2 controller].selected);
215   // Check that there's no hovered tab yet.
216   EXPECT_FALSE([controller_ hoveredTab]);
218   // Set up mouse event on overlap of tab1 + tab2.
219   const CGFloat min_y = NSMinY([tab_strip_.get() frame]) + 1;
221   // Hover over overlap between tab 1 and 2.
222   NSEvent* event =
223       cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(280, min_y),
224                                                 NSMouseMoved, 0);
225   [controller_.get() mouseMoved:event];
226   EXPECT_EQ(tab2, [controller_ hoveredTab]);
228   // Hover over tab 1.
229   event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(260, min_y),
230                                                     NSMouseMoved, 0);
231   [controller_.get() mouseMoved:event];
232   EXPECT_EQ(tab1, [controller_ hoveredTab]);
234   // Hover over tab 2.
235   event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(290, min_y),
236                                                     NSMouseMoved, 0);
237   [controller_.get() mouseMoved:event];
238   EXPECT_EQ(tab2, [controller_ hoveredTab]);
241 TEST_F(TabStripControllerTest, CorrectTitleAndToolTipTextFromSetTabTitle) {
242   using content::MediaStreamDevice;
243   using content::MediaStreamDevices;
244   using content::MediaStreamUI;
246   TabView* const tab = CreateTab();
247   TabController* const tabController = [tab controller];
248   WebContents* const contents = model_->GetActiveWebContents();
250   // Initially, tab title and tooltip text are equivalent.
251   EXPECT_EQ(TAB_MEDIA_STATE_NONE,
252             chrome::GetTabMediaStateForContents(contents));
253   [controller_ setTabTitle:tabController withContents:contents];
254   NSString* const baseTitle = [tabController title];
255   EXPECT_NSEQ(baseTitle, [tabController toolTip]);
257   // Simulate the start of tab video capture.  Tab title remains the same, but
258   // the tooltip text should include the following appended: 1) a line break;
259   // 2) a non-empty string with a localized description of the media state.
260   scoped_refptr<MediaStreamCaptureIndicator> indicator =
261       MediaCaptureDevicesDispatcher::GetInstance()->
262           GetMediaStreamCaptureIndicator();
263   const MediaStreamDevice dummyVideoCaptureDevice(
264       content::MEDIA_TAB_VIDEO_CAPTURE, "dummy_id", "dummy name");
265   scoped_ptr<MediaStreamUI> streamUi(indicator->RegisterMediaStream(
266       contents, MediaStreamDevices(1, dummyVideoCaptureDevice)));
267   streamUi->OnStarted(base::Bind(&base::DoNothing));
268   EXPECT_EQ(TAB_MEDIA_STATE_CAPTURING,
269             chrome::GetTabMediaStateForContents(contents));
270   [controller_ setTabTitle:tabController withContents:contents];
271   EXPECT_NSEQ(baseTitle, [tabController title]);
272   NSString* const toolTipText = [tabController toolTip];
273   if ([baseTitle length] > 0) {
274     EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, [baseTitle length]),
275                               [toolTipText rangeOfString:baseTitle]));
276     EXPECT_TRUE(NSEqualRanges(NSMakeRange([baseTitle length], 1),
277                               [toolTipText rangeOfString:@"\n"]));
278     EXPECT_LT([baseTitle length] + 1, [toolTipText length]);
279   } else {
280     EXPECT_LT(0u, [toolTipText length]);
281   }
283   // Simulate the end of tab video capture.  Tab title and tooltip should become
284   // equivalent again.
285   streamUi.reset();
286   EXPECT_EQ(TAB_MEDIA_STATE_NONE,
287             chrome::GetTabMediaStateForContents(contents));
288   [controller_ setTabTitle:tabController withContents:contents];
289   EXPECT_NSEQ(baseTitle, [tabController title]);
290   EXPECT_NSEQ(baseTitle, [tabController toolTip]);
293 TEST_F(TabStripControllerTest, TabCloseDuringDrag) {
294   TabController* tab;
295   // The TabController gets autoreleased when created, but is owned by the
296   // tab strip model. Use a ScopedNSAutoreleasePool to get a truly weak ref
297   // to it to test that -maybeStartDrag:forTab: can handle that properly.
298   {
299     base::mac::ScopedNSAutoreleasePool pool;
300     tab = [CreateTab() controller];
301   }
303   // Schedule a task to close all the tabs and stop the drag, before the call to
304   // -maybeStartDrag:forTab:, which starts a nested event loop. This task will
305   // run in that nested event loop, which shouldn't crash.
306   base::scoped_nsobject<TestClosureRunner> runner([[TestClosureRunner alloc]
307       initWithClosure:base::Bind(&TabStripControllerTest::CloseTabsAndEndDrag,
308                                  base::Unretained(this))]);
309   [runner scheduleDelayedRun];
311   NSEvent* event =
312       cocoa_test_event_utils::LeftMouseDownAtPoint(NSZeroPoint);
313   [[controller_ dragController] maybeStartDrag:event forTab:tab];
316 TEST_F(TabStripControllerTest, ViewAccessibility_Contents) {
317   NSArray* attrs = [tab_strip_ accessibilityAttributeNames];
318   ASSERT_TRUE([attrs containsObject:NSAccessibilityContentsAttribute]);
320   // Create two tabs and ensure they exist in the contents array.
321   TabView* tab1 = CreateTab();
322   TabView* tab2 = CreateTab();
323   NSObject* contents =
324       [tab_strip_ accessibilityAttributeValue:NSAccessibilityContentsAttribute];
325   DCHECK([contents isKindOfClass:[NSArray class]]);
326   NSArray* contentsArray = static_cast<NSArray*>(contents);
327   ASSERT_TRUE([contentsArray containsObject:tab1]);
328   ASSERT_TRUE([contentsArray containsObject:tab2]);
331 TEST_F(TabStripControllerTest, ViewAccessibility_Value) {
332   NSArray* attrs = [tab_strip_ accessibilityAttributeNames];
333   ASSERT_TRUE([attrs containsObject:NSAccessibilityValueAttribute]);
335   // Create two tabs and ensure the active one gets returned.
336   TabView* tab1 = CreateTab();
337   TabView* tab2 = CreateTab();
338   EXPECT_FALSE([tab1 controller].selected);
339   EXPECT_TRUE([tab2 controller].selected);
340   NSObject* value =
341       [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute];
342   EXPECT_EQ(tab2, value);
344   model_->ActivateTabAt(0, false);
345   EXPECT_TRUE([tab1 controller].selected);
346   EXPECT_FALSE([tab2 controller].selected);
347   value =
348       [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute];
349   EXPECT_EQ(tab1, value);
352 }  // namespace