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/strings/sys_string_conversions.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/sync/glue/session_model_associator.h"
11 #include "chrome/browser/sync/glue/device_info.h"
12 #include "chrome/browser/sync/profile_sync_service_factory.h"
13 #include "chrome/browser/sync/sessions2/sessions_sync_manager.h"
14 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
15 #include "chrome/browser/ui/cocoa/run_loop_testing.h"
16 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
17 #import "chrome/browser/ui/cocoa/view_resizer_pong.h"
18 #import "chrome/browser/ui/cocoa/wrench_menu/wrench_menu_controller.h"
19 #include "chrome/browser/ui/toolbar/recent_tabs_builder_test_helper.h"
20 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
21 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/test/base/testing_profile.h"
24 #include "grit/generated_resources.h"
25 #include "grit/theme_resources.h"
26 #include "sync/api/fake_sync_change_processor.h"
27 #include "sync/api/sync_error_factory_mock.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "testing/gtest_mac.h"
31 #include "testing/platform_test.h"
32 #include "ui/base/l10n/l10n_util.h"
36 class MockWrenchMenuModel : public WrenchMenuModel {
38 MockWrenchMenuModel() : WrenchMenuModel() {}
39 ~MockWrenchMenuModel() {
40 // This dirty, ugly hack gets around a bug in the test. In
41 // ~WrenchMenuModel(), there's a call to TabstripModel::RemoveObserver(this)
42 // which mysteriously leads to this crash: http://crbug.com/49206 . It
43 // seems that the vector of observers is getting hosed somewhere between
44 // |-[ToolbarController dealloc]| and ~MockWrenchMenuModel(). This line
45 // short-circuits the parent destructor to avoid this crash.
46 tab_strip_model_ = NULL;
48 MOCK_METHOD2(ExecuteCommand, void(int command_id, int event_flags));
51 class DummyRouter : public browser_sync::LocalSessionEventRouter {
53 virtual ~DummyRouter() {}
54 virtual void StartRoutingTo(
55 browser_sync::LocalSessionEventHandler* handler) OVERRIDE {}
56 virtual void Stop() OVERRIDE {}
59 class WrenchMenuControllerTest
60 : public CocoaProfileTest,
61 public browser_sync::SessionsSyncManager::SyncInternalApiDelegate {
63 virtual void SetUp() OVERRIDE {
64 CocoaProfileTest::SetUp();
65 ASSERT_TRUE(browser());
67 controller_.reset([[WrenchMenuController alloc] initWithBrowser:browser()]);
68 fake_model_.reset(new MockWrenchMenuModel);
70 if (CommandLine::ForCurrentProcess()->HasSwitch(
71 switches::kEnableSyncSessionsV2)) {
72 manager_.reset(new browser_sync::SessionsSyncManager(
75 scoped_ptr<browser_sync::LocalSessionEventRouter>(
77 manager_->MergeDataAndStartSyncing(
79 syncer::SyncDataList(),
80 scoped_ptr<syncer::SyncChangeProcessor>(
81 new syncer::FakeSyncChangeProcessor),
82 scoped_ptr<syncer::SyncErrorFactory>(
83 new syncer::SyncErrorFactoryMock));
85 ProfileSyncService* sync_service =
86 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile());
87 associator_.reset(new browser_sync::SessionModelAssociator(
89 associator_->SetCurrentMachineTagForTesting(
90 GetLocalSyncCacheGUID());
94 virtual scoped_ptr<browser_sync::DeviceInfo> GetLocalDeviceInfo()
96 return scoped_ptr<browser_sync::DeviceInfo>(
97 new browser_sync::DeviceInfo(GetLocalSyncCacheGUID(),
101 sync_pb::SyncEnums_DeviceType_TYPE_LINUX));
104 virtual std::string GetLocalSyncCacheGUID() const OVERRIDE {
105 return "WrenchMenuControllerTest";
108 void RegisterRecentTabs(RecentTabsBuilderTestHelper* helper) {
109 if (CommandLine::ForCurrentProcess()->HasSwitch(
110 switches::kEnableSyncSessionsV2)) {
111 helper->ExportToSessionsSyncManager(manager_.get());
113 helper->ExportToSessionModelAssociator(associator_.get());
117 browser_sync::OpenTabsUIDelegate* GetOpenTabsDelegate() {
118 if (CommandLine::ForCurrentProcess()->HasSwitch(
119 switches::kEnableSyncSessionsV2)) {
120 return manager_.get();
122 return associator_.get();
126 virtual void TearDown() OVERRIDE {
131 CocoaProfileTest::TearDown();
134 WrenchMenuController* controller() {
135 return controller_.get();
138 base::scoped_nsobject<WrenchMenuController> controller_;
140 scoped_ptr<MockWrenchMenuModel> fake_model_;
143 // TODO(tim): Bug 98892. Remove associator_ once sessions2 is the default.
144 scoped_ptr<browser_sync::SessionModelAssociator> associator_;
145 scoped_ptr<browser_sync::SessionsSyncManager> manager_;
148 TEST_F(WrenchMenuControllerTest, Initialized) {
149 EXPECT_TRUE([controller() menu]);
150 EXPECT_GE([[controller() menu] numberOfItems], 5);
153 TEST_F(WrenchMenuControllerTest, DispatchSimple) {
154 base::scoped_nsobject<NSButton> button([[NSButton alloc] init]);
155 [button setTag:IDC_ZOOM_PLUS];
157 // Set fake model to test dispatching.
158 EXPECT_CALL(*fake_model_, ExecuteCommand(IDC_ZOOM_PLUS, 0));
159 [controller() setModel:fake_model_.get()];
161 [controller() dispatchWrenchMenuCommand:button.get()];
162 chrome::testing::NSRunLoopRunAllPending();
165 TEST_F(WrenchMenuControllerTest, RecentTabsFavIcon) {
166 RecentTabsBuilderTestHelper recent_tabs_builder;
167 recent_tabs_builder.AddSession();
168 recent_tabs_builder.AddWindow(0);
169 recent_tabs_builder.AddTab(0, 0);
170 RegisterRecentTabs(&recent_tabs_builder);
172 RecentTabsSubMenuModel recent_tabs_sub_menu_model(
173 NULL, browser(), GetOpenTabsDelegate());
174 fake_model_->AddSubMenuWithStringId(
175 IDC_RECENT_TABS_MENU, IDS_RECENT_TABS_MENU,
176 &recent_tabs_sub_menu_model);
178 [controller() setModel:fake_model_.get()];
179 NSMenu* menu = [controller() menu];
180 [controller() updateRecentTabsSubmenu];
182 NSString* title = l10n_util::GetNSStringWithFixup(IDS_RECENT_TABS_MENU);
183 NSMenu* recent_tabs_menu = [[menu itemWithTitle:title] submenu];
184 EXPECT_TRUE(recent_tabs_menu);
185 EXPECT_EQ(6, [recent_tabs_menu numberOfItems]);
187 // Send a icon changed event and verify that the icon is updated.
188 gfx::Image icon(ResourceBundle::GetSharedInstance().GetNativeImageNamed(
189 IDR_BOOKMARKS_FAVICON));
190 recent_tabs_sub_menu_model.SetIcon(3, icon);
191 EXPECT_NSNE(icon.ToNSImage(), [[recent_tabs_menu itemAtIndex:3] image]);
192 recent_tabs_sub_menu_model.GetMenuModelDelegate()->OnIconChanged(3);
193 EXPECT_TRUE([[recent_tabs_menu itemAtIndex:3] image]);
194 EXPECT_NSEQ(icon.ToNSImage(), [[recent_tabs_menu itemAtIndex:3] image]);
200 TEST_F(WrenchMenuControllerTest, RecentTabsElideTitle) {
201 // Add 1 session with 1 window and 2 tabs.
202 RecentTabsBuilderTestHelper recent_tabs_builder;
203 recent_tabs_builder.AddSession();
204 recent_tabs_builder.AddWindow(0);
205 base::string16 tab1_short_title = base::ASCIIToUTF16("Short");
206 recent_tabs_builder.AddTabWithInfo(0, 0, base::Time::Now(), tab1_short_title);
207 base::string16 tab2_long_title = base::ASCIIToUTF16(
208 "Very very very very very very very very very very very very long");
209 recent_tabs_builder.AddTabWithInfo(0, 0,
210 base::Time::Now() - base::TimeDelta::FromMinutes(10), tab2_long_title);
211 RegisterRecentTabs(&recent_tabs_builder);
213 RecentTabsSubMenuModel recent_tabs_sub_menu_model(
214 NULL, browser(), GetOpenTabsDelegate());
215 fake_model_->AddSubMenuWithStringId(
216 IDC_RECENT_TABS_MENU, IDS_RECENT_TABS_MENU,
217 &recent_tabs_sub_menu_model);
219 [controller() setModel:fake_model_.get()];
220 NSMenu* menu = [controller() menu];
221 [controller() updateRecentTabsSubmenu];
223 NSString* title = l10n_util::GetNSStringWithFixup(IDS_RECENT_TABS_MENU);
224 NSMenu* recent_tabs_menu = [[menu itemWithTitle:title] submenu];
225 EXPECT_TRUE(recent_tabs_menu);
226 EXPECT_EQ(7, [recent_tabs_menu numberOfItems]);
228 // Index 0: restore tabs menu item.
229 NSString* restore_tab_label = l10n_util::FixUpWindowsStyleLabel(
230 recent_tabs_sub_menu_model.GetLabelAt(0));
231 EXPECT_NSEQ(restore_tab_label, [[recent_tabs_menu itemAtIndex:0] title]);
233 // Item 1: separator.
234 EXPECT_TRUE([[recent_tabs_menu itemAtIndex:1] isSeparatorItem]);
236 // Item 2: window title.
238 base::SysUTF16ToNSString(recent_tabs_sub_menu_model.GetLabelAt(2)),
239 [[recent_tabs_menu itemAtIndex:2] title]);
241 // Item 3: short tab title.
242 EXPECT_NSEQ(base::SysUTF16ToNSString(tab1_short_title),
243 [[recent_tabs_menu itemAtIndex:3] title]);
245 // Item 4: long tab title.
246 NSString* tab2_actual_title = [[recent_tabs_menu itemAtIndex:4] title];
247 NSUInteger title_length = [tab2_actual_title length];
248 EXPECT_GT(tab2_long_title.size(), title_length);
249 NSString* actual_substring =
250 [tab2_actual_title substringToIndex:title_length - 1];
251 NSString* expected_substring = [base::SysUTF16ToNSString(tab2_long_title)
252 substringToIndex:title_length - 1];
253 EXPECT_NSEQ(expected_substring, actual_substring);
259 // Verify that |RecentTabsMenuModelDelegate| is deleted before the model
261 TEST_F(WrenchMenuControllerTest, RecentTabDeleteOrder) {
262 [controller_ menuNeedsUpdate:[controller_ menu]];
263 // If the delete order is wrong then the test will crash on exit.