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/memory/scoped_nsobject.h"
8#include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
9#import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
10#import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
11#include "testing/gtest/include/gtest/gtest.h"
12#import "testing/gtest_mac.h"
13#include "testing/platform_test.h"
14
15// Implements the target interface for the tab, which gets sent messages when
16// the tab is clicked on by the user and when its close box is clicked.
17@interface TabControllerTestTarget : NSObject<TabControllerTarget> {
18 @private
19  bool selected_;
20  bool closed_;
21}
22- (bool)selected;
23- (bool)closed;
24@end
25
26@implementation TabControllerTestTarget
27- (bool)selected {
28  return selected_;
29}
30- (bool)closed {
31  return closed_;
32}
33- (void)selectTab:(id)sender {
34  selected_ = true;
35}
36- (void)closeTab:(id)sender {
37  closed_ = true;
38}
39- (void)mouseTimer:(NSTimer*)timer {
40  // Fire the mouseUp to break the TabView drag loop.
41  NSEvent* current = [NSApp currentEvent];
42  NSWindow* window = [timer userInfo];
43  NSEvent* up = [NSEvent mouseEventWithType:NSLeftMouseUp
44                                   location:[current locationInWindow]
45                              modifierFlags:0
46                                  timestamp:[current timestamp]
47                               windowNumber:[window windowNumber]
48                                    context:nil
49                                eventNumber:0
50                                 clickCount:1
51                                   pressure:1.0];
52  [window postEvent:up atStart:YES];
53}
54- (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
55          forController:(TabController*)controller {
56}
57- (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
58           forController:(TabController*)controller {
59  return NO;
60}
61@end
62
63namespace {
64
65CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) {
66  return NSMinX(subFrame) - NSMinX(superFrame);
67}
68
69CGFloat RightMargin(NSRect superFrame, NSRect subFrame) {
70  return NSMaxX(superFrame) - NSMaxX(subFrame);
71}
72
73// The dragging code in TabView makes heavy use of autorelease pools so
74// inherit from CocoaTest to have one created for us.
75class TabControllerTest : public CocoaTest {
76 public:
77  TabControllerTest() { }
78};
79
80// Tests creating the controller, sticking it in a window, and removing it.
81TEST_F(TabControllerTest, Creation) {
82  NSWindow* window = test_window();
83  scoped_nsobject<TabController> controller([[TabController alloc] init]);
84  [[window contentView] addSubview:[controller view]];
85  EXPECT_TRUE([controller tabView]);
86  EXPECT_EQ([[controller view] window], window);
87  [[controller view] display];  // Test drawing to ensure nothing leaks/crashes.
88  [[controller view] removeFromSuperview];
89}
90
91// Tests sending it a close message and ensuring that the target/action get
92// called. Mimics the user clicking on the close button in the tab.
93TEST_F(TabControllerTest, Close) {
94  NSWindow* window = test_window();
95  scoped_nsobject<TabController> controller([[TabController alloc] init]);
96  [[window contentView] addSubview:[controller view]];
97
98  scoped_nsobject<TabControllerTestTarget> target(
99      [[TabControllerTestTarget alloc] init]);
100  EXPECT_FALSE([target closed]);
101  [controller setTarget:target];
102  EXPECT_EQ(target.get(), [controller target]);
103
104  [controller closeTab:nil];
105  EXPECT_TRUE([target closed]);
106
107  [[controller view] removeFromSuperview];
108}
109
110// Tests setting the |selected| property via code.
111TEST_F(TabControllerTest, APISelection) {
112  NSWindow* window = test_window();
113  scoped_nsobject<TabController> controller([[TabController alloc] init]);
114  [[window contentView] addSubview:[controller view]];
115
116  EXPECT_FALSE([controller selected]);
117  [controller setSelected:YES];
118  EXPECT_TRUE([controller selected]);
119
120  [[controller view] removeFromSuperview];
121}
122
123// Tests that setting the title of a tab sets the tooltip as well.
124TEST_F(TabControllerTest, ToolTip) {
125  NSWindow* window = test_window();
126
127  scoped_nsobject<TabController> controller([[TabController alloc] init]);
128  [[window contentView] addSubview:[controller view]];
129
130  EXPECT_TRUE([[controller toolTip] length] == 0);
131  NSString *tooltip_string = @"Some text to use as a tab title";
132  [controller setTitle:tooltip_string];
133  EXPECT_NSEQ(tooltip_string, [controller toolTip]);
134}
135
136// Tests setting the |loading| property via code.
137TEST_F(TabControllerTest, Loading) {
138  NSWindow* window = test_window();
139  scoped_nsobject<TabController> controller([[TabController alloc] init]);
140  [[window contentView] addSubview:[controller view]];
141
142  EXPECT_EQ(kTabDone, [controller loadingState]);
143  [controller setLoadingState:kTabWaiting];
144  EXPECT_EQ(kTabWaiting, [controller loadingState]);
145  [controller setLoadingState:kTabLoading];
146  EXPECT_EQ(kTabLoading, [controller loadingState]);
147  [controller setLoadingState:kTabDone];
148  EXPECT_EQ(kTabDone, [controller loadingState]);
149
150  [[controller view] removeFromSuperview];
151}
152
153// Tests selecting the tab with the mouse click and ensuring the target/action
154// get called.
155TEST_F(TabControllerTest, UserSelection) {
156  NSWindow* window = test_window();
157
158  // Create a tab at a known location in the window that we can click on
159  // to activate selection.
160  scoped_nsobject<TabController> controller([[TabController alloc] init]);
161  [[window contentView] addSubview:[controller view]];
162  NSRect frame = [[controller view] frame];
163  frame.size.width = [TabController minTabWidth];
164  frame.origin = NSMakePoint(0, 0);
165  [[controller view] setFrame:frame];
166
167  // Set the target and action.
168  scoped_nsobject<TabControllerTestTarget> target(
169      [[TabControllerTestTarget alloc] init]);
170  EXPECT_FALSE([target selected]);
171  [controller setTarget:target];
172  [controller setAction:@selector(selectTab:)];
173  EXPECT_EQ(target.get(), [controller target]);
174  EXPECT_EQ(@selector(selectTab:), [controller action]);
175
176  // In order to track a click, we have to fake a mouse down and a mouse
177  // up, but the down goes into a tight drag loop. To break the loop, we have
178  // to fire a timer that sends a mouse up event while the "drag" is ongoing.
179  [NSTimer scheduledTimerWithTimeInterval:0.1
180                                   target:target.get()
181                                 selector:@selector(mouseTimer:)
182                                 userInfo:window
183                                  repeats:NO];
184  NSEvent* current = [NSApp currentEvent];
185  NSPoint click_point = NSMakePoint(frame.size.width / 2,
186                                    frame.size.height / 2);
187  NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown
188                                     location:click_point
189                                modifierFlags:0
190                                    timestamp:[current timestamp]
191                                 windowNumber:[window windowNumber]
192                                      context:nil
193                                  eventNumber:0
194                                   clickCount:1
195                                     pressure:1.0];
196  [[controller view] mouseDown:down];
197
198  // Check our target was told the tab got selected.
199  EXPECT_TRUE([target selected]);
200
201  [[controller view] removeFromSuperview];
202}
203
204TEST_F(TabControllerTest, IconCapacity) {
205  NSWindow* window = test_window();
206  scoped_nsobject<TabController> controller([[TabController alloc] init]);
207  [[window contentView] addSubview:[controller view]];
208  int cap = [controller iconCapacity];
209  EXPECT_GE(cap, 1);
210
211  NSRect frame = [[controller view] frame];
212  frame.size.width += 500;
213  [[controller view] setFrame:frame];
214  int newcap = [controller iconCapacity];
215  EXPECT_GT(newcap, cap);
216}
217
218TEST_F(TabControllerTest, ShouldShowIcon) {
219  NSWindow* window = test_window();
220  scoped_nsobject<TabController> controller([[TabController alloc] init]);
221  [[window contentView] addSubview:[controller view]];
222  int cap = [controller iconCapacity];
223  EXPECT_GT(cap, 0);
224
225  // Tab is minimum width, both icon and close box should be hidden.
226  NSRect frame = [[controller view] frame];
227  frame.size.width = [TabController minTabWidth];
228  [[controller view] setFrame:frame];
229  EXPECT_FALSE([controller shouldShowIcon]);
230  EXPECT_FALSE([controller shouldShowCloseButton]);
231
232  // Setting the icon when tab is at min width should not show icon (bug 18359).
233  scoped_nsobject<NSView> newIcon(
234      [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 16, 16)]);
235  [controller setIconView:newIcon.get()];
236  EXPECT_TRUE([newIcon isHidden]);
237
238  // Tab is at selected minimum width. Since it's selected, the close box
239  // should be visible.
240  [controller setSelected:YES];
241  frame = [[controller view] frame];
242  frame.size.width = [TabController minSelectedTabWidth];
243  [[controller view] setFrame:frame];
244  EXPECT_FALSE([controller shouldShowIcon]);
245  EXPECT_TRUE([newIcon isHidden]);
246  EXPECT_TRUE([controller shouldShowCloseButton]);
247
248  // Test expanding the tab to max width and ensure the icon and close box
249  // get put back, even when de-selected.
250  frame.size.width = [TabController maxTabWidth];
251  [[controller view] setFrame:frame];
252  EXPECT_TRUE([controller shouldShowIcon]);
253  EXPECT_FALSE([newIcon isHidden]);
254  EXPECT_TRUE([controller shouldShowCloseButton]);
255  [controller setSelected:NO];
256  EXPECT_TRUE([controller shouldShowIcon]);
257  EXPECT_TRUE([controller shouldShowCloseButton]);
258
259  cap = [controller iconCapacity];
260  EXPECT_GT(cap, 0);
261}
262
263TEST_F(TabControllerTest, Menu) {
264  NSWindow* window = test_window();
265  scoped_nsobject<TabController> controller([[TabController alloc] init]);
266  [[window contentView] addSubview:[controller view]];
267  int cap = [controller iconCapacity];
268  EXPECT_GT(cap, 0);
269
270  // Asking the view for its menu should yield a valid menu.
271  NSMenu* menu = [[controller view] menu];
272  EXPECT_TRUE(menu);
273  EXPECT_GT([menu numberOfItems], 0);
274}
275
276// Tests that the title field is correctly positioned and sized when the
277// view is resized.
278TEST_F(TabControllerTest, TitleViewLayout) {
279  NSWindow* window = test_window();
280
281  scoped_nsobject<TabController> controller([[TabController alloc] init]);
282  [[window contentView] addSubview:[controller view]];
283  NSRect tabFrame = [[controller view] frame];
284  tabFrame.size.width = [TabController maxTabWidth];
285  [[controller view] setFrame:tabFrame];
286
287  const NSRect originalTabFrame = [[controller view] frame];
288  const NSRect originalIconFrame = [[controller iconView] frame];
289  const NSRect originalCloseFrame = [[controller closeButton] frame];
290  const NSRect originalTitleFrame = [[controller titleView] frame];
291
292  // Sanity check the start state.
293  EXPECT_FALSE([[controller iconView] isHidden]);
294  EXPECT_FALSE([[controller closeButton] isHidden]);
295  EXPECT_GT(NSWidth([[controller view] frame]),
296            NSWidth([[controller titleView] frame]));
297
298  // Resize the tab so that that the it shrinks.
299  tabFrame.size.width = [TabController minTabWidth];
300  [[controller view] setFrame:tabFrame];
301
302  // The icon view and close button should be hidden and the title view should
303  // be resize to take up their space.
304  EXPECT_TRUE([[controller iconView] isHidden]);
305  EXPECT_TRUE([[controller closeButton] isHidden]);
306  EXPECT_GT(NSWidth([[controller view] frame]),
307            NSWidth([[controller titleView] frame]));
308  EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame),
309            LeftMargin([[controller view] frame],
310                       [[controller titleView] frame]));
311  EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame),
312            RightMargin([[controller view] frame],
313                        [[controller titleView] frame]));
314
315  // Resize the tab so that that the it grows.
316  tabFrame.size.width = [TabController maxTabWidth] * 0.75;
317  [[controller view] setFrame:tabFrame];
318
319  // The icon view and close button should be visible again and the title view
320  // should be resized to make room for them.
321  EXPECT_FALSE([[controller iconView] isHidden]);
322  EXPECT_FALSE([[controller closeButton] isHidden]);
323  EXPECT_GT(NSWidth([[controller view] frame]),
324            NSWidth([[controller titleView] frame]));
325  EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame),
326            LeftMargin([[controller view] frame],
327                       [[controller titleView] frame]));
328  EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame),
329            RightMargin([[controller view] frame],
330                        [[controller titleView] frame]));
331}
332
333}  // namespace
334