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