Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / wrench_menu / wrench_menu_controller_unittest.mm
blob57fa5f440358bffdd4229ed7a31cc81c2f0be7c1
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 #include "base/command_line.h"
6 #include "base/mac/scoped_nsobject.h"
7 #include "base/run_loop.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/sync/profile_sync_service_factory.h"
12 #include "chrome/browser/sync/sessions/sessions_sync_manager.h"
13 #include "chrome/browser/ui/browser_list.h"
14 #include "chrome/browser/ui/browser_list_observer.h"
15 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
16 #include "chrome/browser/ui/cocoa/run_loop_testing.h"
17 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
18 #import "chrome/browser/ui/cocoa/view_resizer_pong.h"
19 #import "chrome/browser/ui/cocoa/wrench_menu/wrench_menu_controller.h"
20 #include "chrome/browser/ui/sync/browser_synced_window_delegates_getter.h"
21 #include "chrome/browser/ui/toolbar/recent_tabs_builder_test_helper.h"
22 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
23 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "chrome/test/base/testing_profile.h"
27 #include "components/sync_driver/local_device_info_provider_mock.h"
28 #include "grit/theme_resources.h"
29 #include "sync/api/fake_sync_change_processor.h"
30 #include "sync/api/sync_error_factory_mock.h"
31 #include "testing/gmock/include/gmock/gmock.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "testing/gtest_mac.h"
34 #include "testing/platform_test.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
38 namespace {
40 class MockWrenchMenuModel : public WrenchMenuModel {
41  public:
42   MockWrenchMenuModel() : WrenchMenuModel() {}
43   ~MockWrenchMenuModel() {
44     // This dirty, ugly hack gets around a bug in the test. In
45     // ~WrenchMenuModel(), there's a call to TabstripModel::RemoveObserver(this)
46     // which mysteriously leads to this crash: http://crbug.com/49206 .  It
47     // seems that the vector of observers is getting hosed somewhere between
48     // |-[ToolbarController dealloc]| and ~MockWrenchMenuModel(). This line
49     // short-circuits the parent destructor to avoid this crash.
50     tab_strip_model_ = NULL;
51   }
52   MOCK_METHOD2(ExecuteCommand, void(int command_id, int event_flags));
55 class DummyRouter : public browser_sync::LocalSessionEventRouter {
56  public:
57   ~DummyRouter() override {}
58   void StartRoutingTo(
59       browser_sync::LocalSessionEventHandler* handler) override {}
60   void Stop() override {}
63 class WrenchMenuControllerTest
64     : public CocoaProfileTest {
65  public:
66   WrenchMenuControllerTest()
67       : local_device_(new sync_driver::LocalDeviceInfoProviderMock(
68             "WrenchMenuControllerTest",
69             "Test Machine",
70             "Chromium 10k",
71             "Chrome 10k",
72             sync_pb::SyncEnums_DeviceType_TYPE_LINUX,
73             "device_id")) {
74   }
76   void SetUp() override {
77     CocoaProfileTest::SetUp();
78     ASSERT_TRUE(browser());
80     controller_.reset([[WrenchMenuController alloc] initWithBrowser:browser()]);
81     fake_model_.reset(new MockWrenchMenuModel);
83     manager_.reset(new browser_sync::SessionsSyncManager(
84         profile(),
85         local_device_.get(),
86         scoped_ptr<browser_sync::LocalSessionEventRouter>(
87             new DummyRouter()),
88         scoped_ptr<browser_sync::SyncedWindowDelegatesGetter>(
89             new browser_sync::BrowserSyncedWindowDelegatesGetter())));
90     manager_->MergeDataAndStartSyncing(
91         syncer::SESSIONS,
92         syncer::SyncDataList(),
93         scoped_ptr<syncer::SyncChangeProcessor>(
94             new syncer::FakeSyncChangeProcessor),
95         scoped_ptr<syncer::SyncErrorFactory>(
96             new syncer::SyncErrorFactoryMock));
97   }
99   void RegisterRecentTabs(RecentTabsBuilderTestHelper* helper) {
100     helper->ExportToSessionsSyncManager(manager_.get());
101   }
103   sync_driver::OpenTabsUIDelegate* GetOpenTabsDelegate() {
104     return manager_.get();
105   }
107   void TearDown() override {
108     fake_model_.reset();
109     controller_.reset();
110     manager_.reset();
111     CocoaProfileTest::TearDown();
112   }
114   WrenchMenuController* controller() {
115     return controller_.get();
116   }
118   base::scoped_nsobject<WrenchMenuController> controller_;
120   scoped_ptr<MockWrenchMenuModel> fake_model_;
122  private:
123   scoped_ptr<browser_sync::SessionsSyncManager> manager_;
124   scoped_ptr<sync_driver::LocalDeviceInfoProviderMock> local_device_;
127 TEST_F(WrenchMenuControllerTest, Initialized) {
128   EXPECT_TRUE([controller() menu]);
129   EXPECT_GE([[controller() menu] numberOfItems], 5);
132 TEST_F(WrenchMenuControllerTest, DispatchSimple) {
133   base::scoped_nsobject<NSButton> button([[NSButton alloc] init]);
134   [button setTag:IDC_ZOOM_PLUS];
136   // Set fake model to test dispatching.
137   EXPECT_CALL(*fake_model_, ExecuteCommand(IDC_ZOOM_PLUS, 0));
138   [controller() setModel:fake_model_.get()];
140   [controller() dispatchWrenchMenuCommand:button.get()];
141   chrome::testing::NSRunLoopRunAllPending();
144 TEST_F(WrenchMenuControllerTest, RecentTabsFavIcon) {
145   RecentTabsBuilderTestHelper recent_tabs_builder;
146   recent_tabs_builder.AddSession();
147   recent_tabs_builder.AddWindow(0);
148   recent_tabs_builder.AddTab(0, 0);
149   RegisterRecentTabs(&recent_tabs_builder);
151   RecentTabsSubMenuModel recent_tabs_sub_menu_model(
152       NULL, browser(), GetOpenTabsDelegate());
153   fake_model_->AddSubMenuWithStringId(
154       IDC_RECENT_TABS_MENU, IDS_RECENT_TABS_MENU,
155       &recent_tabs_sub_menu_model);
157   [controller() setModel:fake_model_.get()];
158   NSMenu* menu = [controller() menu];
159   [controller() updateRecentTabsSubmenu];
161   NSString* title = l10n_util::GetNSStringWithFixup(IDS_RECENT_TABS_MENU);
162   NSMenu* recent_tabs_menu = [[menu itemWithTitle:title] submenu];
163   EXPECT_TRUE(recent_tabs_menu);
164   EXPECT_EQ(6, [recent_tabs_menu numberOfItems]);
166   // Send a icon changed event and verify that the icon is updated.
167   gfx::Image icon(ResourceBundle::GetSharedInstance().GetNativeImageNamed(
168       IDR_BOOKMARKS_FAVICON));
169   recent_tabs_sub_menu_model.SetIcon(3, icon);
170   EXPECT_NSNE(icon.ToNSImage(), [[recent_tabs_menu itemAtIndex:3] image]);
171   recent_tabs_sub_menu_model.GetMenuModelDelegate()->OnIconChanged(3);
172   EXPECT_TRUE([[recent_tabs_menu itemAtIndex:3] image]);
173   EXPECT_NSEQ(icon.ToNSImage(), [[recent_tabs_menu itemAtIndex:3] image]);
175   controller_.reset();
176   fake_model_.reset();
179 TEST_F(WrenchMenuControllerTest, RecentTabsElideTitle) {
180   // Add 1 session with 1 window and 2 tabs.
181   RecentTabsBuilderTestHelper recent_tabs_builder;
182   recent_tabs_builder.AddSession();
183   recent_tabs_builder.AddWindow(0);
184   base::string16 tab1_short_title = base::ASCIIToUTF16("Short");
185   recent_tabs_builder.AddTabWithInfo(0, 0, base::Time::Now(), tab1_short_title);
186   base::string16 tab2_long_title = base::ASCIIToUTF16(
187       "Very very very very very very very very very very very very long");
188   recent_tabs_builder.AddTabWithInfo(0, 0,
189       base::Time::Now() - base::TimeDelta::FromMinutes(10), tab2_long_title);
190   RegisterRecentTabs(&recent_tabs_builder);
192   RecentTabsSubMenuModel recent_tabs_sub_menu_model(
193       NULL, browser(), GetOpenTabsDelegate());
194   fake_model_->AddSubMenuWithStringId(
195       IDC_RECENT_TABS_MENU, IDS_RECENT_TABS_MENU,
196       &recent_tabs_sub_menu_model);
198   [controller() setModel:fake_model_.get()];
199   NSMenu* menu = [controller() menu];
200   [controller() updateRecentTabsSubmenu];
202   NSString* title = l10n_util::GetNSStringWithFixup(IDS_RECENT_TABS_MENU);
203   NSMenu* recent_tabs_menu = [[menu itemWithTitle:title] submenu];
204   EXPECT_TRUE(recent_tabs_menu);
205   EXPECT_EQ(7, [recent_tabs_menu numberOfItems]);
207   // Item 1: separator.
208   EXPECT_TRUE([[recent_tabs_menu itemAtIndex:1] isSeparatorItem]);
210   // Index 2: restore tabs menu item.
211   NSString* restore_tab_label = l10n_util::FixUpWindowsStyleLabel(
212       recent_tabs_sub_menu_model.GetLabelAt(2));
213   EXPECT_NSEQ(restore_tab_label, [[recent_tabs_menu itemAtIndex:2] title]);
215   // Item 3: separator.
216   EXPECT_TRUE([[recent_tabs_menu itemAtIndex:3] isSeparatorItem]);
218   // Item 4: window title.
219   EXPECT_NSEQ(
220       base::SysUTF16ToNSString(recent_tabs_sub_menu_model.GetLabelAt(4)),
221       [[recent_tabs_menu itemAtIndex:4] title]);
223   // Item 5: short tab title.
224   EXPECT_NSEQ(base::SysUTF16ToNSString(tab1_short_title),
225               [[recent_tabs_menu itemAtIndex:5] title]);
227   // Item 6: long tab title.
228   NSString* tab2_actual_title = [[recent_tabs_menu itemAtIndex:6] title];
229   NSUInteger title_length = [tab2_actual_title length];
230   EXPECT_GT(tab2_long_title.size(), title_length);
231   NSString* actual_substring =
232       [tab2_actual_title substringToIndex:title_length - 1];
233   NSString* expected_substring = [base::SysUTF16ToNSString(tab2_long_title)
234       substringToIndex:title_length - 1];
235   EXPECT_NSEQ(expected_substring, actual_substring);
237   controller_.reset();
238   fake_model_.reset();
241 // Verify that |RecentTabsMenuModelDelegate| is deleted before the model
242 // it's observing.
243 TEST_F(WrenchMenuControllerTest, RecentTabDeleteOrder) {
244   [controller_ menuNeedsUpdate:[controller_ menu]];
245   // If the delete order is wrong then the test will crash on exit.
248 class BrowserRemovedObserver : public chrome::BrowserListObserver {
249  public:
250   BrowserRemovedObserver() { BrowserList::AddObserver(this); }
251   ~BrowserRemovedObserver() override { BrowserList::RemoveObserver(this); }
252   void WaitUntilBrowserRemoved() { run_loop_.Run(); }
253   void OnBrowserRemoved(Browser* browser) override { run_loop_.Quit(); }
255  private:
256   base::RunLoop run_loop_;
258   DISALLOW_COPY_AND_ASSIGN(BrowserRemovedObserver);
261 // Test that WrenchMenuController can be destroyed after the Browser.
262 // This can happen because the WrenchMenuController's owner (ToolbarController)
263 // can outlive the Browser.
264 TEST_F(WrenchMenuControllerTest, DestroyedAfterBrowser) {
265   BrowserRemovedObserver observer;
266   // This is normally called by ToolbarController, but since |controller_| is
267   // not owned by one, call it here.
268   [controller_ browserWillBeDestroyed];
269   CloseBrowserWindow();
270   observer.WaitUntilBrowserRemoved();
271   // |controller_| is released in TearDown().
274 }  // namespace