Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / toolbar / toolbar_controller_unittest.mm
blob1eeffaed319c00f982ce2e01dc2f6f1a1b578789
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 #import "base/mac/scoped_nsobject.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/run_loop.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/command_updater.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_command_controller.h"
14 #include "chrome/browser/ui/browser_commands.h"
15 #include "chrome/browser/ui/browser_list.h"
16 #include "chrome/browser/ui/browser_list_observer.h"
17 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
18 #import "chrome/browser/ui/cocoa/image_button_cell.h"
19 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
20 #include "chrome/common/pref_names.h"
21 #include "chrome/test/base/testing_profile.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #import "testing/gtest_mac.h"
24 #include "testing/platform_test.h"
26 // An NSView that fakes out hitTest:.
27 @interface HitView : NSView {
28   id hitTestReturn_;
30 @end
32 @implementation HitView
34 - (void)setHitTestReturn:(id)rtn {
35   hitTestReturn_ = rtn;
38 - (NSView *)hitTest:(NSPoint)aPoint {
39   return hitTestReturn_;
42 @end
44 // Records the last command id and enabled state it has received so it can be
45 // queried by the tests to see if we got a notification or not.
46 @interface TestToolbarController : ToolbarController {
47  @private
48   int lastCommand_;  // Id of last received state change.
49   bool lastState_;   // State of last received state change.
51 @property(nonatomic, readonly) int lastCommand;
52 @property(nonatomic, readonly) bool lastState;
53 @end
55 @implementation TestToolbarController
56 @synthesize lastCommand = lastCommand_;
57 @synthesize lastState = lastState_;
58 - (void)enabledStateChangedForCommand:(int)command enabled:(bool)enabled {
59   [super enabledStateChangedForCommand:command enabled:enabled];
60   lastCommand_ = command;
61   lastState_ = enabled;
63 @end
65 namespace {
67 class ToolbarControllerTest : public CocoaProfileTest {
68  public:
70   // Indexes that match the ordering returned by the private ToolbarController
71   // |-toolbarViews| method.
72   enum {
73     kBackIndex, kForwardIndex, kReloadIndex, kHomeIndex,
74     kWrenchIndex, kLocationIndex, kBrowserActionContainerViewIndex
75   };
77   void SetUp() override {
78     CocoaProfileTest::SetUp();
79     ASSERT_TRUE(browser());
81     CommandUpdater* updater =
82         browser()->command_controller()->command_updater();
83     // The default state for the commands is true, set a couple to false to
84     // ensure they get picked up correct on initialization
85     updater->UpdateCommandEnabled(IDC_BACK, false);
86     updater->UpdateCommandEnabled(IDC_FORWARD, false);
87     bar_.reset([[TestToolbarController alloc]
88         initWithCommands:browser()->command_controller()->command_updater()
89                  profile:profile()
90                  browser:browser()]);
91     EXPECT_TRUE([bar_ view]);
92     NSView* parent = [test_window() contentView];
93     [parent addSubview:[bar_ view]];
94   }
96   void TearDown() override {
97     bar_.reset();  // browser() must outlive the ToolbarController.
98     CocoaProfileTest::TearDown();
99   }
101   // Make sure the enabled state of the view is the same as the corresponding
102   // command in the updater. The views are in the declaration order of outlets.
103   void CompareState(CommandUpdater* updater, NSArray* views) {
104     EXPECT_EQ(updater->IsCommandEnabled(IDC_BACK),
105               [[views objectAtIndex:kBackIndex] isEnabled] ? true : false);
106     EXPECT_EQ(updater->IsCommandEnabled(IDC_FORWARD),
107               [[views objectAtIndex:kForwardIndex] isEnabled] ? true : false);
108     EXPECT_EQ(updater->IsCommandEnabled(IDC_RELOAD),
109               [[views objectAtIndex:kReloadIndex] isEnabled] ? true : false);
110     EXPECT_EQ(updater->IsCommandEnabled(IDC_HOME),
111               [[views objectAtIndex:kHomeIndex] isEnabled] ? true : false);
112   }
114   base::scoped_nsobject<TestToolbarController> bar_;
117 TEST_VIEW(ToolbarControllerTest, [bar_ view])
119 // Test the initial state that everything is sync'd up
120 TEST_F(ToolbarControllerTest, InitialState) {
121   CommandUpdater* updater = browser()->command_controller()->command_updater();
122   CompareState(updater, [bar_ toolbarViews]);
125 // Make sure a "titlebar only" toolbar with location bar works.
126 TEST_F(ToolbarControllerTest, TitlebarOnly) {
127   NSView* view = [bar_ view];
129   [bar_ setHasToolbar:NO hasLocationBar:YES];
130   EXPECT_NE(view, [bar_ view]);
132   // Simulate a popup going fullscreen and back by performing the reparenting
133   // that happens during fullscreen transitions
134   NSView* superview = [view superview];
135   [view removeFromSuperview];
136   [superview addSubview:view];
138   [bar_ setHasToolbar:YES hasLocationBar:YES];
139   EXPECT_EQ(view, [bar_ view]);
141   // Leave it off to make sure that's fine
142   [bar_ setHasToolbar:NO hasLocationBar:YES];
145 // Make sure it works in the completely undecorated case.
146 TEST_F(ToolbarControllerTest, NoLocationBar) {
147   NSView* view = [bar_ view];
149   [bar_ setHasToolbar:NO hasLocationBar:NO];
150   EXPECT_NE(view, [bar_ view]);
151   EXPECT_TRUE([[bar_ view] isHidden]);
153   // Simulate a popup going fullscreen and back by performing the reparenting
154   // that happens during fullscreen transitions
155   NSView* superview = [view superview];
156   [view removeFromSuperview];
157   [superview addSubview:view];
160 // Make some changes to the enabled state of a few of the buttons and ensure
161 // that we're still in sync.
162 TEST_F(ToolbarControllerTest, UpdateEnabledState) {
163   EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BACK));
164   EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_FORWARD));
165   chrome::UpdateCommandEnabled(browser(), IDC_BACK, true);
166   chrome::UpdateCommandEnabled(browser(), IDC_FORWARD, true);
167   CommandUpdater* updater = browser()->command_controller()->command_updater();
168   CompareState(updater, [bar_ toolbarViews]);
170   // Change an unwatched command and ensure the last state does not change.
171   updater->UpdateCommandEnabled(IDC_MinimumLabelValue, false);
172   EXPECT_EQ([bar_ lastCommand], IDC_FORWARD);
173   EXPECT_EQ([bar_ lastState], true);
176 // Focus the location bar and make sure that it's the first responder.
177 TEST_F(ToolbarControllerTest, FocusLocation) {
178   NSWindow* window = test_window();
179   [window makeFirstResponder:[window contentView]];
180   EXPECT_EQ([window firstResponder], [window contentView]);
181   [bar_ focusLocationBar:YES];
182   EXPECT_NE([window firstResponder], [window contentView]);
183   NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex];
184   EXPECT_EQ([window firstResponder], [(id)locationBar currentEditor]);
187 TEST_F(ToolbarControllerTest, LoadingState) {
188   // In its initial state, the reload button has a tag of
189   // IDC_RELOAD. When loading, it should be IDC_STOP.
190   NSButton* reload = [[bar_ toolbarViews] objectAtIndex:kReloadIndex];
191   EXPECT_EQ([reload tag], IDC_RELOAD);
192   [bar_ setIsLoading:YES force:YES];
193   EXPECT_EQ([reload tag], IDC_STOP);
194   [bar_ setIsLoading:NO force:YES];
195   EXPECT_EQ([reload tag], IDC_RELOAD);
198 // Check that toggling the state of the home button changes the visible
199 // state of the home button and moves the other items accordingly.
200 TEST_F(ToolbarControllerTest, ToggleHome) {
201   PrefService* prefs = profile()->GetPrefs();
202   bool showHome = prefs->GetBoolean(prefs::kShowHomeButton);
203   NSView* homeButton = [[bar_ toolbarViews] objectAtIndex:kHomeIndex];
204   EXPECT_EQ(showHome, ![homeButton isHidden]);
206   NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex];
207   NSRect originalLocationBarFrame = [locationBar frame];
209   // Toggle the pref and make sure the button changed state and the other
210   // views moved.
211   prefs->SetBoolean(prefs::kShowHomeButton, !showHome);
212   EXPECT_EQ(showHome, [homeButton isHidden]);
213   EXPECT_NE(NSMinX(originalLocationBarFrame), NSMinX([locationBar frame]));
214   EXPECT_NE(NSWidth(originalLocationBarFrame), NSWidth([locationBar frame]));
217 // Ensure that we don't toggle the buttons when we have a strip marked as not
218 // having the full toolbar. Also ensure that the location bar doesn't change
219 // size.
220 TEST_F(ToolbarControllerTest, DontToggleWhenNoToolbar) {
221   [bar_ setHasToolbar:NO hasLocationBar:YES];
222   NSView* homeButton = [[bar_ toolbarViews] objectAtIndex:kHomeIndex];
223   NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex];
224   NSRect locationBarFrame = [locationBar frame];
225   EXPECT_EQ([homeButton isHidden], YES);
226   [bar_ showOptionalHomeButton];
227   EXPECT_EQ([homeButton isHidden], YES);
228   NSRect newLocationBarFrame = [locationBar frame];
229   EXPECT_NSEQ(locationBarFrame, newLocationBarFrame);
230   newLocationBarFrame = [locationBar frame];
231   EXPECT_NSEQ(locationBarFrame, newLocationBarFrame);
234 TEST_F(ToolbarControllerTest, BookmarkBubblePoint) {
235   const NSPoint starPoint = [bar_ bookmarkBubblePoint];
236   const NSRect barFrame =
237       [[bar_ view] convertRect:[[bar_ view] bounds] toView:nil];
239   // Make sure the star is completely inside the location bar.
240   EXPECT_TRUE(NSPointInRect(starPoint, barFrame));
243 TEST_F(ToolbarControllerTest, TranslateBubblePoint) {
244   const NSPoint translatePoint = [bar_ translateBubblePoint];
245   const NSRect barFrame =
246       [[bar_ view] convertRect:[[bar_ view] bounds] toView:nil];
247   EXPECT_TRUE(NSPointInRect(translatePoint, barFrame));
250 TEST_F(ToolbarControllerTest, HoverButtonForEvent) {
251   base::scoped_nsobject<HitView> view(
252       [[HitView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]);
253   [bar_ setView:view];
254   NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
255                                       location:NSMakePoint(10,10)
256                                  modifierFlags:0
257                                      timestamp:0
258                                   windowNumber:0
259                                        context:nil
260                                    eventNumber:0
261                                     clickCount:0
262                                       pressure:0.0];
264   // NOT a match.
265   [view setHitTestReturn:bar_.get()];
266   EXPECT_FALSE([bar_ hoverButtonForEvent:event]);
268   // Not yet...
269   base::scoped_nsobject<NSButton> button([[NSButton alloc] init]);
270   [view setHitTestReturn:button];
271   EXPECT_FALSE([bar_ hoverButtonForEvent:event]);
273   // Now!
274   base::scoped_nsobject<ImageButtonCell> cell(
275       [[ImageButtonCell alloc] init]);
276   [button setCell:cell.get()];
277   EXPECT_TRUE([bar_ hoverButtonForEvent:nil]);
280 class BrowserRemovedObserver : public chrome::BrowserListObserver {
281  public:
282   BrowserRemovedObserver() { BrowserList::AddObserver(this); }
283   ~BrowserRemovedObserver() override { BrowserList::RemoveObserver(this); }
284   void WaitUntilBrowserRemoved() { run_loop_.Run(); }
285   void OnBrowserRemoved(Browser* browser) override { run_loop_.Quit(); }
287  private:
288   base::RunLoop run_loop_;
290   DISALLOW_COPY_AND_ASSIGN(BrowserRemovedObserver);
293 // Test that ToolbarController can be destroyed after the Browser.
294 // This can happen because the ToolbarController is retained by both the
295 // BrowserWindowController and -[ToolbarController view], the latter of which is
296 // autoreleased.
297 TEST_F(ToolbarControllerTest, ToolbarDestroyedAfterBrowser) {
298   BrowserRemovedObserver observer;
299   // This is normally called by BrowserWindowController, but since |bar_| is not
300   // owned by one, call it here.
301   [bar_ browserWillBeDestroyed];
302   CloseBrowserWindow();
303   observer.WaitUntilBrowserRemoved();
304   // |bar_| is released in TearDown().
307 }  // namespace