Chromium Code Reviews| Index: chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc |
| diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..08e611695054701543c16b1a6a56b4b6991b5ee2 |
| --- /dev/null |
| +++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc |
| @@ -0,0 +1,417 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h" |
| + |
| +#include "base/rand_util.h" |
| +#include "base/string_number_conversions.h" |
| +#include "base/stringprintf.h" |
| +#include "chrome/app/chrome_command_ids.h" |
| +#include "chrome/browser/sessions/session_types.h" |
| +#include "chrome/browser/sessions/session_types_test_helper.h" |
| +#include "chrome/browser/sync/glue/session_model_associator.h" |
| +#include "chrome/browser/sync/glue/synced_session.h" |
| +#include "chrome/browser/sync/profile_sync_service_mock.h" |
| +#include "chrome/browser/ui/browser.h" |
| +#include "chrome/browser/ui/browser_tabstrip.h" |
| +#include "chrome/test/base/browser_with_test_window_test.h" |
| +#include "chrome/test/base/menu_model_test.h" |
| +#include "chrome/test/base/testing_profile.h" |
| +#include "grit/generated_resources.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace { |
| + |
| +const char kBaseSessionTag[] = "session_tag"; |
| +const char kBaseSessionName[] = "session_name"; |
| +const char kBaseTabUrl[] = "http://foo/?"; |
| +const char kTabTitleFormat[] = "session=%d;window=%d;tab=%d"; |
| + |
| +struct TabTime { |
| + TabTime(SessionID::id_type session_id, |
| + SessionID::id_type window_id, |
| + int tab_idx, |
| + base::Time timestamp) |
| + : session_id(session_id), |
| + window_id(window_id), |
| + tab_idx(tab_idx), |
| + timestamp(timestamp) {} |
| + |
| + SessionID::id_type session_id; |
| + SessionID::id_type window_id; |
| + int tab_idx; |
| + base::Time timestamp; |
| +}; |
| + |
| +bool SortTabTimesByRecency(const TabTime& t1, const TabTime& t2) { |
| + return t1.timestamp > t2.timestamp; |
| +} |
| + |
| +std::string ToSessionTag(SessionID::id_type session_id) { |
| + return std::string(kBaseSessionTag + base::IntToString(session_id)); |
| +} |
| + |
| +std::string ToSessionName(SessionID::id_type session_id) { |
| + return std::string(kBaseSessionName + base::IntToString(session_id)); |
| +} |
| + |
| +std::string ToTabTitle(SessionID::id_type session_id, |
| + SessionID::id_type window_id, |
| + int tab_idx) { |
| + return base::StringPrintf(kTabTitleFormat, session_id, window_id, tab_idx); |
| +} |
| + |
| +std::string ToTabUrl(SessionID::id_type session_id, |
| + SessionID::id_type window_id, |
| + int tab_idx) { |
| + return std::string(kBaseTabUrl + ToTabTitle(session_id, window_id, tab_idx)); |
| +} |
| + |
| +// Copies parts of MenuModelTest::Delegate and combines them with the |
| +// RecentTabsSubMenuModel since RecentTabsSubMenuModel is a |
| +// SimpleMenuModel::Delegate and not just derived from SimpleMenuModel. |
| +class TestRecentTabsSubMenuModel : public RecentTabsSubMenuModel { |
| + public: |
| + TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider, |
| + Browser* browser, |
| + bool can_restore_tab) |
| + : RecentTabsSubMenuModel(provider, browser), |
| + can_restore_tab_(can_restore_tab), |
| + execute_count_(0), |
| + enable_count_(0) { |
| + } |
| + |
| + TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider, |
| + Browser* browser, |
| + browser_sync::SessionModelAssociator* associator, |
| + bool can_restore_tab) |
| + : RecentTabsSubMenuModel(provider, browser, associator, true), |
| + can_restore_tab_(can_restore_tab), |
| + execute_count_(0), |
| + enable_count_(0) { |
| + } |
| + |
| + // Testing overrides to ui::SimpleMenuModel::Delegate: |
| + virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { |
| + bool val = command_id == IDC_RESTORE_TAB ? can_restore_tab_ : |
| + RecentTabsSubMenuModel::IsCommandIdEnabled(command_id); |
| + if (val) |
| + ++enable_count_; |
| + return val; |
| + } |
| + |
| + virtual void ExecuteCommand(int command_id) OVERRIDE { |
| + ++execute_count_; |
| + } |
| + |
| + bool can_restore_tab_; |
| + int execute_count_; |
| + int mutable enable_count_; // Mutable because IsCommandIdEnabledAt is const. |
| +}; |
| + |
| +class TestSessionModelAssociator{ |
| + public: |
| + TestSessionModelAssociator() |
| + : sync_service_(&profile_), |
| + model_associator_(&sync_service_, true) {} |
| + |
| + void InitWindowPb(int num_tabs, SessionID::id_type* tab_id, |
| + sync_pb::SessionWindow* window_pb) { |
| + for (int i = 0; i < num_tabs; ++i) |
| + window_pb->add_tab((*tab_id)++); |
| + window_pb->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED); |
| + window_pb->set_selected_tab_index(0); |
| + } |
| + |
| + void CreateSession(SessionID::id_type session_id) { |
| + browser_sync::SyncedSession* session = GetSession(session_id); |
| + session->device_type = browser_sync::SyncedSession::TYPE_CHROMEOS; |
| + session->session_name = ToSessionName(session_id); |
| + } |
| + |
| + void PutWindowInSessionAndTracker(SessionID::id_type session_id, |
| + SessionID::id_type window_id, |
| + 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.
|
| + tracker().PutWindowInSession(ToSessionTag(session_id), window_id); |
| + browser_sync::SyncedSession* session = GetSession(session_id); |
| + SessionWindow* window = session->windows[window_id]; |
| + browser_sync::SessionModelAssociator:: |
| + PopulateSessionWindowFromSpecificsForTest( |
| + ToSessionTag(session_id), window_pb, base::Time(), window, |
| + &tracker()); |
| + ASSERT_EQ(static_cast<size_t>(window_pb.tab_size()), window->tabs.size()); |
| + } |
| + |
| + void MakeTabSyncable(SessionID::id_type session_id, |
| + SessionID::id_type window_id, |
| + int tab_idx, |
| + base::Time timestamp) { |
| + 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.
|
| + SessionTab* tab = session->windows[window_id]->tabs[tab_idx]; |
| + tab->navigations.push_back(SessionTypesTestHelper::CreateNavigation( |
| + ToTabUrl(session_id, window_id, tab_idx), |
| + ToTabTitle(session_id, window_id, tab_idx))); |
| + tab->current_navigation_index = 0; |
| + tab->timestamp = timestamp; |
| + session->modified_time = timestamp; |
| + } |
| + |
| + browser_sync::SyncedSession* GetSession(SessionID::id_type session_id) { |
| + return tracker().GetSession(ToSessionTag(session_id)); |
| + } |
| + |
| + browser_sync::SyncedSessionTracker& tracker() { |
| + return model_associator_.GetSyncedSessionTrackerForTest(); |
| + } |
| + |
| + browser_sync::SessionModelAssociator& model_associator() { |
| + return model_associator_; |
| + } |
| + |
| + private: |
| + TestingProfile profile_; |
| + testing::NiceMock<ProfileSyncServiceMock> sync_service_; |
| + browser_sync::SessionModelAssociator model_associator_; |
| +}; |
| + |
| +} // namespace |
| + |
| +class RecentTabsSubMenuModelTest : public BrowserWithTestWindowTest, |
| + public ui::AcceleratorProvider { |
| + public: |
| + // Don't handle accelerators. |
| + virtual bool GetAcceleratorForCommandId( |
| + int command_id, |
| + ui::Accelerator* accelerator) OVERRIDE { |
| + return false; |
| + } |
| +}; |
| + |
| +TEST_F(RecentTabsSubMenuModelTest, NoTabs) { |
| + TestRecentTabsSubMenuModel model(this, browser(), false); |
| + |
| + // Expected menu: |
| + // Menu index Menu items |
| + // -------------------------------------- |
| + // 0 Reopen closed tab |
| + // 1 <separator> |
| + // 2 No tabs from other Devices |
| + |
| + int num_items = model.GetItemCount(); |
| + EXPECT_EQ(3, num_items); |
| + EXPECT_FALSE(model.IsEnabledAt(0)); |
| + EXPECT_FALSE(model.IsEnabledAt(2)); |
| + EXPECT_EQ(0, model.enable_count_); |
| +} |
| + |
| +TEST_F(RecentTabsSubMenuModelTest, ReopenClosedTab) { |
| + TestRecentTabsSubMenuModel model(this, browser(), true); |
| + // Menu contents are identical to NoTabs test. |
| + int num_items = model.GetItemCount(); |
| + EXPECT_EQ(3, num_items); |
| + EXPECT_TRUE(model.IsEnabledAt(0)); |
| + model.ActivatedAt(0); |
| + EXPECT_FALSE(model.IsEnabledAt(2)); |
| + EXPECT_EQ(1, model.enable_count_); |
| + EXPECT_EQ(1, model.execute_count_); |
| +} |
| + |
| +TEST_F(RecentTabsSubMenuModelTest, OtherDevices) { |
| + TestSessionModelAssociator associator; |
| + SessionID::id_type id = 0; |
| + // Tabs are populated in decreasing timestamp. |
| + base::Time now = base::Time::Now(); |
| + |
| + // Create 1st session : 1 window, 3 tabs |
| + SessionID::id_type session_id = id++; |
| + associator.CreateSession(session_id); |
| + SessionID::id_type window_id = id++; |
| + sync_pb::SessionWindow window_pb; |
| + associator.InitWindowPb(3, &id, &window_pb); |
| + associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); |
| + base::Time timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); |
| + associator.MakeTabSyncable(session_id, window_id, 0, timestamp); |
| + associator.MakeTabSyncable(session_id, window_id, 1, |
| + timestamp - base::TimeDelta::FromMinutes(1)); |
| + associator.MakeTabSyncable(session_id, window_id, 2, |
| + timestamp - base::TimeDelta::FromMinutes(2)); |
| + 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.
|
| + |
| + // Create 2nd session : 2 windows, 1 tab in 1st window, 2 tabs in 2nd window |
| + session_id = id++; |
| + associator.CreateSession(session_id); |
| + window_id = id++; |
| + sync_pb::SessionWindow window_pb0; |
| + associator.InitWindowPb(1, &id, &window_pb0); |
| + associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb0); |
| + timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); |
| + associator.MakeTabSyncable(session_id, window_id, 0, timestamp); |
| + ASSERT_EQ(1U, associator.tracker().num_synced_tabs(ToSessionTag(session_id))); |
| + window_id = id++; |
| + sync_pb::SessionWindow window_pb1; |
| + associator.InitWindowPb(2, &id, &window_pb1); |
| + associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb1); |
| + timestamp = now - base::TimeDelta::FromMinutes(window_id * 10); |
| + associator.MakeTabSyncable(session_id, window_id, 0, timestamp); |
| + associator.MakeTabSyncable(session_id, window_id, 1, |
| + timestamp - base::TimeDelta::FromMinutes(1)); |
| + ASSERT_EQ(3U, associator.tracker().num_synced_tabs(ToSessionTag(session_id))); |
| + |
| + ASSERT_EQ(2U, associator.tracker().num_synced_sessions()); |
|
akalin
2012/11/10 00:08:40
GetAllForeignSessions()?
kuan
2012/11/10 19:34:46
Done.
|
| + |
| + TestRecentTabsSubMenuModel model(this, browser(), |
| + &associator.model_associator(), true); |
| + |
| + // Expected menu: |
| + // - first inserted tab is most recent and hence is top |
| + // Menu index Menu items |
| + // -------------------------------------- |
| + // 0 Reopen closed tab |
| + // 1 <separator> |
| + // 2 <section header for 1st session> |
| + // 3-5 <3 tabs of the only window of session 0> |
| + // 6 <separator> |
| + // 7 <section header for 2nd session> |
| + // 8 <the only tab of window 0 of session 1> |
| + // 9-10 <2 tabs of window 1 of session 2> |
| + |
| + int num_items = model.GetItemCount(); |
| + EXPECT_EQ(11, num_items); |
| + model.ActivatedAt(0); |
| + EXPECT_TRUE(model.IsEnabledAt(0)); |
| + model.ActivatedAt(3); |
| + EXPECT_TRUE(model.IsEnabledAt(3)); |
| + model.ActivatedAt(4); |
| + EXPECT_TRUE(model.IsEnabledAt(4)); |
| + model.ActivatedAt(5); |
| + EXPECT_TRUE(model.IsEnabledAt(5)); |
| + model.ActivatedAt(8); |
| + EXPECT_TRUE(model.IsEnabledAt(8)); |
| + model.ActivatedAt(9); |
| + EXPECT_TRUE(model.IsEnabledAt(9)); |
| + model.ActivatedAt(10); |
| + EXPECT_TRUE(model.IsEnabledAt(10)); |
| + EXPECT_EQ(7, model.enable_count_); |
| + EXPECT_EQ(7, model.execute_count_); |
| +} |
| + |
| +TEST_F(RecentTabsSubMenuModelTest, MaxSessionsAndRecency) { |
| + TestSessionModelAssociator associator; |
| + SessionID::id_type id = 0; |
| + base::Time now = base::Time::Now(); |
| + |
| + // Create 4 sessions : each session has 1 window with 1 tab each . |
| + std::vector<TabTime> tab_times; |
| + for (int i = 0; i < 4; ++i) { |
| + SessionID::id_type session_id = id++; |
| + associator.CreateSession(session_id); |
| + SessionID::id_type window_id = id++; |
| + sync_pb::SessionWindow window_pb; |
| + associator.InitWindowPb(1, &id, &window_pb); |
| + associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); |
| + base::Time timestamp = now + |
| + base::TimeDelta::FromMinutes(base::RandUint64()); |
| + associator.MakeTabSyncable(session_id, window_id, 0, timestamp); |
| + tab_times.push_back(TabTime(session_id, window_id, 0, timestamp)); |
| + ASSERT_EQ(1U, |
| + associator.tracker().num_synced_tabs(ToSessionTag(session_id))); |
| + } |
| + ASSERT_EQ(4U, associator.tracker().num_synced_sessions()); |
| + |
| + TestRecentTabsSubMenuModel model(this, browser(), |
| + &associator.model_associator(), true); |
| + |
| + // Expected menu: |
| + // - max sessions is 3, so only 3 most-recent sessions will show |
| + // Menu index Menu items |
| + // -------------------------------------- |
| + // 0 Reopen closed tab |
| + // 1 <separator> |
| + // 2 <section header for 1st session> |
| + // 3 <the only tab of the only window of session 3> |
| + // 4 <separator> |
| + // 5 <section header for 2nd session> |
| + // 6 <the only tab of the only window of session 2> |
| + // 7 <separator> |
| + // 8 <section header for 3rd session> |
| + // 9 <the only tab of the only window of session 1> |
| + |
| + int num_items = model.GetItemCount(); |
| + EXPECT_EQ(10, num_items); |
| + |
| + sort(tab_times.begin(), tab_times.end(), SortTabTimesByRecency); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[0].session_id, |
| + tab_times[0].window_id, |
| + tab_times[0].tab_idx)), |
| + model.GetLabelAt(3)); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[1].session_id, |
| + tab_times[1].window_id, |
| + tab_times[1].tab_idx)), |
| + model.GetLabelAt(6)); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[2].session_id, |
| + tab_times[2].window_id, |
| + tab_times[2].tab_idx)), |
| + model.GetLabelAt(9)); |
| +} |
| + |
| +TEST_F(RecentTabsSubMenuModelTest, MaxTabsPerSessionAndRecency) { |
| + TestSessionModelAssociator associator; |
| + SessionID::id_type id = 0; |
| + base::Time now = base::Time::Now(); |
| + |
| + // Create a session: 2 windows with 5 tabs each. |
| + std::vector<TabTime> tab_times; |
| + SessionID::id_type session_id = id++; |
| + associator.CreateSession(session_id); |
| + for (int j = 0; j < 2; ++j) { |
| + SessionID::id_type window_id = id++; |
| + sync_pb::SessionWindow window_pb; |
| + associator.InitWindowPb(5, &id, &window_pb); |
| + associator.PutWindowInSessionAndTracker(session_id, window_id, window_pb); |
| + for (int k = 0; k < 5; ++k) { |
| + base::Time timestamp(now + |
| + base::TimeDelta::FromMinutes(base::RandUint64())); |
| + associator.MakeTabSyncable(session_id, window_id, k, timestamp); |
| + tab_times.push_back(TabTime(session_id, window_id, k, timestamp)); |
| + } |
| + } |
| + ASSERT_EQ(10U, |
| + associator.tracker().num_synced_tabs(ToSessionTag(session_id))); |
| + ASSERT_EQ(1U, associator.tracker().num_synced_sessions()); |
| + |
| + TestRecentTabsSubMenuModel model(this, browser(), |
| + &associator.model_associator(), true); |
| + |
| + // Expected menu: |
| + // - max tabs per session is 4, so only 4 most-recent tabs will show, |
| + // independent of which window they came from |
| + // Menu index Menu items |
| + // -------------------------------------- |
| + // 0 Reopen closed tab |
| + // 1 <separator> |
| + // 2 <section header for session> |
| + // 3-6 <4 most-recent tabs of session> |
| + |
| + int num_items = model.GetItemCount(); |
| + EXPECT_EQ(7, num_items); |
| + |
| + sort(tab_times.begin(), tab_times.end(), SortTabTimesByRecency); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[0].session_id, |
| + tab_times[0].window_id, |
| + tab_times[0].tab_idx)), |
| + model.GetLabelAt(3)); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[1].session_id, |
| + tab_times[1].window_id, |
| + tab_times[1].tab_idx)), |
| + model.GetLabelAt(4)); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[2].session_id, |
| + tab_times[2].window_id, |
| + tab_times[2].tab_idx)), |
| + model.GetLabelAt(5)); |
| + EXPECT_EQ(UTF8ToUTF16(ToTabTitle(tab_times[3].session_id, |
| + tab_times[3].window_id, |
| + tab_times[3].tab_idx)), |
| + model.GetLabelAt(6)); |
| +} |