Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h" | |
| 6 | |
| 7 #include "base/rand_util.h" | |
| 8 #include "base/string_number_conversions.h" | |
| 9 #include "base/stringprintf.h" | |
| 10 #include "chrome/app/chrome_command_ids.h" | |
| 11 #include "chrome/browser/sessions/session_types.h" | |
| 12 #include "chrome/browser/sessions/session_types_test_helper.h" | |
| 13 #include "chrome/browser/sync/glue/session_model_associator.h" | |
| 14 #include "chrome/browser/sync/glue/synced_session.h" | |
| 15 #include "chrome/browser/sync/profile_sync_service_mock.h" | |
| 16 #include "chrome/browser/ui/browser.h" | |
| 17 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 18 #include "chrome/test/base/browser_with_test_window_test.h" | |
| 19 #include "chrome/test/base/menu_model_test.h" | |
| 20 #include "chrome/test/base/testing_profile.h" | |
| 21 #include "grit/generated_resources.h" | |
| 22 #include "testing/gmock/include/gmock/gmock.h" | |
| 23 #include "testing/gtest/include/gtest/gtest.h" | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 const char kBaseSessionTag[] = "session_tag"; | |
| 28 const char kBaseSessionName[] = "session_name"; | |
| 29 const char kBaseTabUrl[] = "http://foo/?"; | |
| 30 const char kTabTitleFormat[] = "session=%d;window=%d;tab=%d"; | |
| 31 | |
| 32 struct TabTime { | |
| 33 TabTime(SessionID::id_type session_id, | |
| 34 SessionID::id_type window_id, | |
| 35 int tab_idx, | |
| 36 base::Time timestamp) | |
| 37 : session_id(session_id), | |
| 38 window_id(window_id), | |
| 39 tab_idx(tab_idx), | |
| 40 timestamp(timestamp) {} | |
| 41 | |
| 42 SessionID::id_type session_id; | |
| 43 SessionID::id_type window_id; | |
| 44 int tab_idx; | |
| 45 base::Time timestamp; | |
| 46 }; | |
| 47 | |
| 48 bool SortTabTimesByRecency(const TabTime& t1, const TabTime& t2) { | |
| 49 return t1.timestamp > t2.timestamp; | |
| 50 } | |
| 51 | |
| 52 std::string ToSessionTag(SessionID::id_type session_id) { | |
| 53 return std::string(kBaseSessionTag + base::IntToString(session_id)); | |
| 54 } | |
| 55 | |
| 56 std::string ToSessionName(SessionID::id_type session_id) { | |
| 57 return std::string(kBaseSessionName + base::IntToString(session_id)); | |
| 58 } | |
| 59 | |
| 60 std::string ToTabTitle(SessionID::id_type session_id, | |
| 61 SessionID::id_type window_id, | |
| 62 int tab_idx) { | |
| 63 return base::StringPrintf(kTabTitleFormat, session_id, window_id, tab_idx); | |
| 64 } | |
| 65 | |
| 66 std::string ToTabUrl(SessionID::id_type session_id, | |
| 67 SessionID::id_type window_id, | |
| 68 int tab_idx) { | |
| 69 return std::string(kBaseTabUrl + ToTabTitle(session_id, window_id, tab_idx)); | |
| 70 } | |
| 71 | |
| 72 // Copies parts of MenuModelTest::Delegate and combines them with the | |
| 73 // RecentTabsSubMenuModel since RecentTabsSubMenuModel is a | |
| 74 // SimpleMenuModel::Delegate and not just derived from SimpleMenuModel. | |
| 75 class TestRecentTabsSubMenuModel : public RecentTabsSubMenuModel { | |
| 76 public: | |
| 77 TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider, | |
| 78 Browser* browser, | |
| 79 bool can_restore_tab) | |
| 80 : RecentTabsSubMenuModel(provider, browser), | |
| 81 can_restore_tab_(can_restore_tab), | |
| 82 execute_count_(0), | |
| 83 enable_count_(0) { | |
| 84 } | |
| 85 | |
| 86 TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider, | |
| 87 Browser* browser, | |
| 88 browser_sync::SessionModelAssociator* associator, | |
| 89 bool can_restore_tab) | |
| 90 : RecentTabsSubMenuModel(provider, browser, associator, true), | |
| 91 can_restore_tab_(can_restore_tab), | |
| 92 execute_count_(0), | |
| 93 enable_count_(0) { | |
| 94 } | |
| 95 | |
| 96 // Testing overrides to ui::SimpleMenuModel::Delegate: | |
| 97 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { | |
| 98 bool val = command_id == IDC_RESTORE_TAB ? can_restore_tab_ : | |
| 99 RecentTabsSubMenuModel::IsCommandIdEnabled(command_id); | |
| 100 if (val) | |
| 101 ++enable_count_; | |
| 102 return val; | |
| 103 } | |
| 104 | |
| 105 virtual void ExecuteCommand(int command_id) OVERRIDE { | |
| 106 ++execute_count_; | |
| 107 } | |
| 108 | |
| 109 bool can_restore_tab_; | |
| 110 int execute_count_; | |
| 111 int mutable enable_count_; // Mutable because IsCommandIdEnabledAt is const. | |
| 112 }; | |
| 113 | |
| 114 class TestSessionModelAssociator{ | |
| 115 public: | |
| 116 TestSessionModelAssociator() | |
| 117 : sync_service_(&profile_), | |
| 118 model_associator_(&sync_service_, true) {} | |
| 119 | |
| 120 void InitWindowPb(int num_tabs, SessionID::id_type* tab_id, | |
| 121 sync_pb::SessionWindow* window_pb) { | |
| 122 for (int i = 0; i < num_tabs; ++i) | |
| 123 window_pb->add_tab((*tab_id)++); | |
| 124 window_pb->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED); | |
| 125 window_pb->set_selected_tab_index(0); | |
| 126 } | |
| 127 | |
| 128 void CreateSession(SessionID::id_type session_id) { | |
| 129 browser_sync::SyncedSession* session = GetSession(session_id); | |
| 130 session->device_type = browser_sync::SyncedSession::TYPE_CHROMEOS; | |
| 131 session->session_name = ToSessionName(session_id); | |
| 132 } | |
| 133 | |
| 134 void PutWindowInSessionAndTracker(SessionID::id_type session_id, | |
| 135 SessionID::id_type window_id, | |
| 136 const sync_pb::SessionWindow& window_pb) { | |
|
akalin
2012/11/10 00:08:40
actually, can't you just use the public function A
kuan
2012/11/10 17:07:04
i tried implementing building the SessionSpecifics
kuan
2012/11/10 19:34:46
Done.
| |
| 137 tracker().PutWindowInSession(ToSessionTag(session_id), window_id); | |
| 138 browser_sync::SyncedSession* session = GetSession(session_id); | |
| 139 SessionWindow* window = session->windows[window_id]; | |
| 140 browser_sync::SessionModelAssociator:: | |
| 141 PopulateSessionWindowFromSpecificsForTest( | |
| 142 ToSessionTag(session_id), window_pb, base::Time(), window, | |
| 143 &tracker()); | |
| 144 ASSERT_EQ(static_cast<size_t>(window_pb.tab_size()), window->tabs.size()); | |
| 145 } | |
| 146 | |
| 147 void MakeTabSyncable(SessionID::id_type session_id, | |
| 148 SessionID::id_type window_id, | |
| 149 int tab_idx, | |
| 150 base::Time timestamp) { | |
| 151 browser_sync::SyncedSession* session = GetSession(session_id); | |
|
akalin
2012/11/10 00:08:40
can you do this using public functions? Maybe use
kuan
2012/11/10 19:34:46
Done.
| |
| 152 SessionTab* tab = session->windows[window_id]->tabs[tab_idx]; | |
| 153 tab->navigations.push_back(SessionTypesTestHelper::CreateNavigation( | |
| 154 ToTabUrl(session_id, window_id, tab_idx), | |
| 155 ToTabTitle(session_id, window_id, tab_idx))); | |
| 156 tab->current_navigation_index = 0; | |
| 157 tab->timestamp = timestamp; | |
| 158 session->modified_time = timestamp; | |
| 159 } | |
| 160 | |
| 161 browser_sync::SyncedSession* GetSession(SessionID::id_type session_id) { | |
| 162 return tracker().GetSession(ToSessionTag(session_id)); | |
| 163 } | |
| 164 | |
| 165 browser_sync::SyncedSessionTracker& tracker() { | |
| 166 return model_associator_.GetSyncedSessionTrackerForTest(); | |
| 167 } | |
| 168 | |
| 169 browser_sync::SessionModelAssociator& model_associator() { | |
| 170 return model_associator_; | |
| 171 } | |
| 172 | |
| 173 private: | |
| 174 TestingProfile profile_; | |
| 175 testing::NiceMock<ProfileSyncServiceMock> sync_service_; | |
| 176 browser_sync::SessionModelAssociator model_associator_; | |
| 177 }; | |
| 178 | |
| 179 } // namespace | |
| 180 | |
| 181 class RecentTabsSubMenuModelTest : public BrowserWithTestWindowTest, | |
| 182 public ui::AcceleratorProvider { | |
| 183 public: | |
| 184 // Don't handle accelerators. | |
| 185 virtual bool GetAcceleratorForCommandId( | |
| 186 int command_id, | |
| 187 ui::Accelerator* accelerator) OVERRIDE { | |
| 188 return false; | |
| 189 } | |
| 190 }; | |
| 191 | |
| 192 TEST_F(RecentTabsSubMenuModelTest, NoTabs) { | |
| 193 TestRecentTabsSubMenuModel model(this, browser(), false); | |
| 194 | |
| 195 // Expected menu: | |
| 196 // Menu index Menu items | |
| 197 // -------------------------------------- | |
| 198 // 0 Reopen closed tab | |
| 199 // 1 <separator> | |
| 200 // 2 No tabs from other Devices | |
| 201 | |
| 202 int num_items = model.GetItemCount(); | |
| 203 EXPECT_EQ(3, num_items); | |
| 204 EXPECT_FALSE(model.IsEnabledAt(0)); | |
| 205 EXPECT_FALSE(model.IsEnabledAt(2)); | |
| 206 EXPECT_EQ(0, model.enable_count_); | |
| 207 } | |
| 208 | |
| 209 TEST_F(RecentTabsSubMenuModelTest, ReopenClosedTab) { | |
| 210 TestRecentTabsSubMenuModel model(this, browser(), true); | |
| 211 // Menu contents are identical to NoTabs test. | |
| 212 int num_items = model.GetItemCount(); | |
| 213 EXPECT_EQ(3, num_items); | |
| 214 EXPECT_TRUE(model.IsEnabledAt(0)); | |
| 215 model.ActivatedAt(0); | |
| 216 EXPECT_FALSE(model.IsEnabledAt(2)); | |
| 217 EXPECT_EQ(1, model.enable_count_); | |
| 218 EXPECT_EQ(1, model.execute_count_); | |
| 219 } | |
| 220 | |
| 221 TEST_F(RecentTabsSubMenuModelTest, OtherDevices) { | |
| 222 TestSessionModelAssociator associator; | |
| 223 SessionID::id_type id = 0; | |
| 224 // Tabs are populated in decreasing timestamp. | |
| 225 base::Time now = base::Time::Now(); | |
| 226 | |
| 227 // Create 1st session : 1 window, 3 tabs | |
| 228 SessionID::id_type session_id = id++; | |
| 229 associator.CreateSession(session_id); | |
| 230 SessionID::id_type window_id = id++; | |
| 231 sync_pb::SessionWindow window_pb; | |
| 232 associator.InitWindowPb(3, &id, &window_pb); | |
| 233 associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); | |
| 234 base::Time timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); | |
| 235 associator.MakeTabSyncable(session_id, window_id, 0, timestamp); | |
| 236 associator.MakeTabSyncable(session_id, window_id, 1, | |
| 237 timestamp - base::TimeDelta::FromMinutes(1)); | |
| 238 associator.MakeTabSyncable(session_id, window_id, 2, | |
| 239 timestamp - base::TimeDelta::FromMinutes(2)); | |
| 240 ASSERT_EQ(3U, associator.tracker().num_synced_tabs(ToSessionTag(session_id))); | |
|
akalin
2012/11/10 00:08:40
can you get the num synced tabs using GetForeignSe
kuan
2012/11/10 19:34:46
Done.
| |
| 241 | |
| 242 // Create 2nd session : 2 windows, 1 tab in 1st window, 2 tabs in 2nd window | |
| 243 session_id = id++; | |
| 244 associator.CreateSession(session_id); | |
| 245 window_id = id++; | |
| 246 sync_pb::SessionWindow window_pb0; | |
| 247 associator.InitWindowPb(1, &id, &window_pb0); | |
| 248 associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb0); | |
| 249 timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); | |
| 250 associator.MakeTabSyncable(session_id, window_id, 0, timestamp); | |
| 251 ASSERT_EQ(1U, associator.tracker().num_synced_tabs(ToSessionTag(session_id))); | |
| 252 window_id = id++; | |
| 253 sync_pb::SessionWindow window_pb1; | |
| 254 associator.InitWindowPb(2, &id, &window_pb1); | |
| 255 associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb1); | |
| 256 timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); | |
| 257 associator.MakeTabSyncable(session_id, window_id, 0, timestamp); | |
| 258 associator.MakeTabSyncable(session_id, window_id, 1, | |
| 259 timestamp - base::TimeDelta::FromMinutes(1)); | |
| 260 ASSERT_EQ(3U, associator.tracker().num_synced_tabs(ToSessionTag(session_id))); | |
| 261 | |
| 262 ASSERT_EQ(2U, associator.tracker().num_synced_sessions()); | |
|
akalin
2012/11/10 00:08:40
GetAllForeignSessions()?
kuan
2012/11/10 19:34:46
Done.
| |
| 263 | |
| 264 TestRecentTabsSubMenuModel model(this, browser(), | |
| 265 &associator.model_associator(), true); | |
| 266 | |
| 267 // Expected menu: | |
| 268 // - first inserted tab is most recent and hence is top | |
| 269 // Menu index Menu items | |
| 270 // -------------------------------------- | |
| 271 // 0 Reopen closed tab | |
| 272 // 1 <separator> | |
| 273 // 2 <section header for 1st session> | |
| 274 // 3-5 <3 tabs of the only window of session 0> | |
| 275 // 6 <separator> | |
| 276 // 7 <section header for 2nd session> | |
| 277 // 8 <the only tab of window 0 of session 1> | |
| 278 // 9-10 <2 tabs of window 1 of session 2> | |
| 279 | |
| 280 int num_items = model.GetItemCount(); | |
| 281 EXPECT_EQ(11, num_items); | |
| 282 model.ActivatedAt(0); | |
| 283 EXPECT_TRUE(model.IsEnabledAt(0)); | |
| 284 model.ActivatedAt(3); | |
| 285 EXPECT_TRUE(model.IsEnabledAt(3)); | |
| 286 model.ActivatedAt(4); | |
| 287 EXPECT_TRUE(model.IsEnabledAt(4)); | |
| 288 model.ActivatedAt(5); | |
| 289 EXPECT_TRUE(model.IsEnabledAt(5)); | |
| 290 model.ActivatedAt(8); | |
| 291 EXPECT_TRUE(model.IsEnabledAt(8)); | |
| 292 model.ActivatedAt(9); | |
| 293 EXPECT_TRUE(model.IsEnabledAt(9)); | |
| 294 model.ActivatedAt(10); | |
| 295 EXPECT_TRUE(model.IsEnabledAt(10)); | |
| 296 EXPECT_EQ(7, model.enable_count_); | |
| 297 EXPECT_EQ(7, model.execute_count_); | |
| 298 } | |
| 299 | |
| 300 TEST_F(RecentTabsSubMenuModelTest, MaxSessionsAndRecency) { | |
| 301 TestSessionModelAssociator associator; | |
| 302 SessionID::id_type id = 0; | |
| 303 base::Time now = base::Time::Now(); | |
| 304 | |
| 305 // Create 4 sessions : each session has 1 window with 1 tab each . | |
| 306 std::vector<TabTime> tab_times; | |
| 307 for (int i = 0; i < 4; ++i) { | |
| 308 SessionID::id_type session_id = id++; | |
| 309 associator.CreateSession(session_id); | |
| 310 SessionID::id_type window_id = id++; | |
| 311 sync_pb::SessionWindow window_pb; | |
| 312 associator.InitWindowPb(1, &id, &window_pb); | |
| 313 associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); | |
| 314 base::Time timestamp = now + | |
| 315 base::TimeDelta::FromMinutes(base::RandUint64()); | |
| 316 associator.MakeTabSyncable(session_id, window_id, 0, timestamp); | |
| 317 tab_times.push_back(TabTime(session_id, window_id, 0, timestamp)); | |
| 318 ASSERT_EQ(1U, | |
| 319 associator.tracker().num_synced_tabs(ToSessionTag(session_id))); | |
| 320 } | |
| 321 ASSERT_EQ(4U, associator.tracker().num_synced_sessions()); | |
| 322 | |
| 323 TestRecentTabsSubMenuModel model(this, browser(), | |
| 324 &associator.model_associator(), true); | |
| 325 | |
| 326 // Expected menu: | |
| 327 // - max sessions is 3, so only 3 most-recent sessions will show | |
| 328 // Menu index Menu items | |
| 329 // -------------------------------------- | |
| 330 // 0 Reopen closed tab | |
| 331 // 1 <separator> | |
| 332 // 2 <section header for 1st session> | |
| 333 // 3 <the only tab of the only window of session 3> | |
| 334 // 4 <separator> | |
| 335 // 5 <section header for 2nd session> | |
| 336 // 6 <the only tab of the only window of session 2> | |
| 337 // 7 <separator> | |
| 338 // 8 <section header for 3rd session> | |
| 339 // 9 <the only tab of the only window of session 1> | |
| 340 | |
| 341 int num_items = model.GetItemCount(); | |
| 342 EXPECT_EQ(10, num_items); | |
| 343 | |
| 344 sort(tab_times.begin(), tab_times.end(), SortTabTimesByRecency); | |
| 345 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[0].session_id, | |
| 346 tab_times[0].window_id, | |
| 347 tab_times[0].tab_idx)), | |
| 348 model.GetLabelAt(3)); | |
| 349 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[1].session_id, | |
| 350 tab_times[1].window_id, | |
| 351 tab_times[1].tab_idx)), | |
| 352 model.GetLabelAt(6)); | |
| 353 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[2].session_id, | |
| 354 tab_times[2].window_id, | |
| 355 tab_times[2].tab_idx)), | |
| 356 model.GetLabelAt(9)); | |
| 357 } | |
| 358 | |
| 359 TEST_F(RecentTabsSubMenuModelTest, MaxTabsPerSessionAndRecency) { | |
| 360 TestSessionModelAssociator associator; | |
| 361 SessionID::id_type id = 0; | |
| 362 base::Time now = base::Time::Now(); | |
| 363 | |
| 364 // Create a session: 2 windows with 5 tabs each. | |
| 365 std::vector<TabTime> tab_times; | |
| 366 SessionID::id_type session_id = id++; | |
| 367 associator.CreateSession(session_id); | |
| 368 for (int j = 0; j < 2; ++j) { | |
| 369 SessionID::id_type window_id = id++; | |
| 370 sync_pb::SessionWindow window_pb; | |
| 371 associator.InitWindowPb(5, &id, &window_pb); | |
| 372 associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); | |
| 373 for (int k = 0; k < 5; ++k) { | |
| 374 base::Time timestamp(now + | |
| 375 base::TimeDelta::FromMinutes(base::RandUint64())); | |
| 376 associator.MakeTabSyncable(session_id, window_id, k, timestamp); | |
| 377 tab_times.push_back(TabTime(session_id, window_id, k, timestamp)); | |
| 378 } | |
| 379 } | |
| 380 ASSERT_EQ(10U, | |
| 381 associator.tracker().num_synced_tabs(ToSessionTag(session_id))); | |
| 382 ASSERT_EQ(1U, associator.tracker().num_synced_sessions()); | |
| 383 | |
| 384 TestRecentTabsSubMenuModel model(this, browser(), | |
| 385 &associator.model_associator(), true); | |
| 386 | |
| 387 // Expected menu: | |
| 388 // - max tabs per session is 4, so only 4 most-recent tabs will show, | |
| 389 // independent of which window they came from | |
| 390 // Menu index Menu items | |
| 391 // -------------------------------------- | |
| 392 // 0 Reopen closed tab | |
| 393 // 1 <separator> | |
| 394 // 2 <section header for session> | |
| 395 // 3-6 <4 most-recent tabs of session> | |
| 396 | |
| 397 int num_items = model.GetItemCount(); | |
| 398 EXPECT_EQ(7, num_items); | |
| 399 | |
| 400 sort(tab_times.begin(), tab_times.end(), SortTabTimesByRecency); | |
| 401 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[0].session_id, | |
| 402 tab_times[0].window_id, | |
| 403 tab_times[0].tab_idx)), | |
| 404 model.GetLabelAt(3)); | |
| 405 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[1].session_id, | |
| 406 tab_times[1].window_id, | |
| 407 tab_times[1].tab_idx)), | |
| 408 model.GetLabelAt(4)); | |
| 409 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[2].session_id, | |
| 410 tab_times[2].window_id, | |
| 411 tab_times[2].tab_idx)), | |
| 412 model.GetLabelAt(5)); | |
| 413 EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[3].session_id, | |
| 414 tab_times[3].window_id, | |
| 415 tab_times[3].tab_idx)), | |
| 416 model.GetLabelAt(6)); | |
| 417 } | |
| OLD | NEW |