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 "chrome/app/chrome_command_ids.h"
8#include "chrome/browser/sessions/session_types.h"
9#include "chrome/browser/sessions/persistent_tab_restore_service.h"
10#include "chrome/browser/sessions/tab_restore_service_factory.h"
11#include "chrome/browser/sync/glue/session_model_associator.h"
12#include "chrome/browser/sync/glue/synced_session.h"
13#include "chrome/browser/sync/profile_sync_service_mock.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_tabstrip.h"
16#include "chrome/browser/ui/tabs/tab_strip_model.h"
17#include "chrome/browser/ui/toolbar/recent_tabs_builder_test_helper.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
25namespace {
26
27// This copies parts of MenuModelTest::Delegate and combines them with the
28// RecentTabsSubMenuModel since RecentTabsSubMenuModel is a
29// SimpleMenuModel::Delegate and not just derived from SimpleMenuModel.
30class TestRecentTabsSubMenuModel : public RecentTabsSubMenuModel {
31 public:
32  TestRecentTabsSubMenuModel(ui::AcceleratorProvider* provider,
33                             Browser* browser,
34                             browser_sync::SessionModelAssociator* associator)
35      : RecentTabsSubMenuModel(provider, browser, associator),
36        execute_count_(0),
37        enable_count_(0) {
38  }
39
40  // Testing overrides to ui::SimpleMenuModel::Delegate:
41  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
42    bool val = RecentTabsSubMenuModel::IsCommandIdEnabled(command_id);
43    if (val)
44      ++enable_count_;
45    return val;
46  }
47
48  virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
49    ++execute_count_;
50  }
51
52  int execute_count_;
53  int mutable enable_count_;  // Mutable because IsCommandIdEnabledAt is const.
54};
55
56}  // namespace
57
58class RecentTabsSubMenuModelTest : public BrowserWithTestWindowTest {
59 public:
60   RecentTabsSubMenuModelTest()
61       : sync_service_(&testing_profile_),
62         associator_(&sync_service_, true) {
63    associator_.SetCurrentMachineTagForTesting("RecentTabsSubMenuModelTest");
64  }
65
66  static BrowserContextKeyedService* GetTabRestoreService(
67      content::BrowserContext* browser_context) {
68    // Ownership is tranfered to the profile.
69    return new PersistentTabRestoreService(
70        Profile::FromBrowserContext(browser_context), NULL);;
71  }
72 private:
73  TestingProfile testing_profile_;
74  testing::NiceMock<ProfileSyncServiceMock> sync_service_;
75
76 protected:
77  browser_sync::SessionModelAssociator associator_;
78};
79
80// Test disabled "Reopen closed tab" with no foreign tabs.
81TEST_F(RecentTabsSubMenuModelTest, NoTabs) {
82  TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
83
84  // Expected menu:
85  // Menu index  Menu items
86  // --------------------------------------
87  // 0           Reopen closed tab
88  // 1           <separator>
89  // 2           No tabs from other Devices
90
91  int num_items = model.GetItemCount();
92  EXPECT_EQ(3, num_items);
93  EXPECT_FALSE(model.IsEnabledAt(0));
94  EXPECT_FALSE(model.IsEnabledAt(2));
95  EXPECT_EQ(0, model.enable_count_);
96}
97
98// Test enabled "Reopen closed tab" with no foreign tabs.
99TEST_F(RecentTabsSubMenuModelTest, ReopenClosedTab) {
100  TabRestoreServiceFactory::GetInstance()->SetTestingFactory(
101      browser()->profile(), RecentTabsSubMenuModelTest::GetTabRestoreService);
102
103  // Add a tab and close it.
104  GURL url1("http://foo/1");
105  AddTab(browser(), url1);
106  browser()->tab_strip_model()->CloseWebContentsAt(
107      0, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
108
109  TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
110  // Expected menu:
111  // Menu index  Menu items
112  // --------------------------------------
113  // 0           Recently Closed Header
114  // 1           Single entry to restore
115  // 2           <separator>
116  // 3           No tabs from other Devices
117  int num_items = model.GetItemCount();
118  EXPECT_EQ(4, num_items);
119  EXPECT_FALSE(model.IsEnabledAt(0));
120  EXPECT_TRUE(model.IsEnabledAt(1));
121  model.ActivatedAt(1);
122  EXPECT_FALSE(model.IsEnabledAt(3));
123  EXPECT_EQ(1, model.enable_count_);
124  EXPECT_EQ(1, model.execute_count_);
125}
126
127// Test enabled "Reopen closed tab" with multiple sessions, multiple windows,
128// and multiple enabled tabs from other devices.
129TEST_F(RecentTabsSubMenuModelTest, OtherDevices) {
130  // Tabs are populated in decreasing timestamp.
131  base::Time timestamp = base::Time::Now();
132  const base::TimeDelta time_delta = base::TimeDelta::FromMinutes(10);
133
134  RecentTabsBuilderTestHelper recent_tabs_builder;
135
136  // Create 1st session : 1 window, 3 tabs
137  recent_tabs_builder.AddSession();
138  recent_tabs_builder.AddWindow(0);
139  for (int i = 0; i < 3; ++i) {
140    timestamp -= time_delta;
141    recent_tabs_builder.AddTabWithInfo(0, 0, timestamp, string16());
142  }
143
144  // Create 2nd session : 2 windows, 1 tab in 1st window, 2 tabs in 2nd window
145  recent_tabs_builder.AddSession();
146  recent_tabs_builder.AddWindow(1);
147  recent_tabs_builder.AddWindow(1);
148  timestamp -= time_delta;
149  recent_tabs_builder.AddTabWithInfo(1, 0, timestamp, string16());
150  timestamp -= time_delta;
151  recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, string16());
152  timestamp -= time_delta;
153  recent_tabs_builder.AddTabWithInfo(1, 1, timestamp, string16());
154
155  recent_tabs_builder.RegisterRecentTabs(&associator_);
156
157  // Verify that data is populated correctly in RecentTabsSubMenuModel.
158  // Expected menu:
159  // - first inserted tab is most recent and hence is top
160  // Menu index  Menu items
161  // --------------------------------------
162  // 0           Reopen closed tab
163  // 1           <separator>
164  // 2           <section header for 1st session>
165  // 3-5         <3 tabs of the only window of session 0>
166  // 6           <separator>
167  // 7           <section header for 2nd session>
168  // 8           <the only tab of window 0 of session 1>
169  // 9-10        <2 tabs of window 1 of session 2>
170  // 11          <separator>
171  // 12          More...
172
173  TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
174  int num_items = model.GetItemCount();
175  EXPECT_EQ(13, num_items);
176  model.ActivatedAt(0);
177  EXPECT_FALSE(model.IsEnabledAt(0));
178  model.ActivatedAt(3);
179  EXPECT_TRUE(model.IsEnabledAt(3));
180  model.ActivatedAt(4);
181  EXPECT_TRUE(model.IsEnabledAt(4));
182  model.ActivatedAt(5);
183  EXPECT_TRUE(model.IsEnabledAt(5));
184  model.ActivatedAt(8);
185  EXPECT_TRUE(model.IsEnabledAt(8));
186  model.ActivatedAt(9);
187  EXPECT_TRUE(model.IsEnabledAt(9));
188  model.ActivatedAt(10);
189  EXPECT_TRUE(model.IsEnabledAt(10));
190  EXPECT_TRUE(model.IsEnabledAt(12));
191  EXPECT_EQ(7, model.enable_count_);
192  EXPECT_EQ(7, model.execute_count_);
193}
194
195TEST_F(RecentTabsSubMenuModelTest, MaxSessionsAndRecency) {
196  // Create 4 sessions : each session has 1 window with 1 tab each.
197  RecentTabsBuilderTestHelper recent_tabs_builder;
198  for (int s = 0; s < 4; ++s) {
199    recent_tabs_builder.AddSession();
200    recent_tabs_builder.AddWindow(s);
201    recent_tabs_builder.AddTab(s, 0);
202  }
203  recent_tabs_builder.RegisterRecentTabs(&associator_);
204
205  // Verify that data is populated correctly in RecentTabsSubMenuModel.
206  // Expected menu:
207  // - max sessions is 3, so only 3 most-recent sessions will show.
208  // Menu index  Menu items
209  // --------------------------------------
210  // 0           Reopen closed tab
211  // 1           <separator>
212  // 2           <section header for 1st session>
213  // 3           <the only tab of the only window of session 3>
214  // 4           <separator>
215  // 5           <section header for 2nd session>
216  // 6           <the only tab of the only window of session 2>
217  // 7           <separator>
218  // 8           <section header for 3rd session>
219  // 9           <the only tab of the only window of session 1>
220  // 10          <separator>
221  // 11          More...
222
223  TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
224  int num_items = model.GetItemCount();
225  EXPECT_EQ(12, num_items);
226
227  std::vector<string16> tab_titles =
228      recent_tabs_builder.GetTabTitlesSortedByRecency();
229  EXPECT_EQ(tab_titles[0], model.GetLabelAt(3));
230  EXPECT_EQ(tab_titles[1], model.GetLabelAt(6));
231  EXPECT_EQ(tab_titles[2], model.GetLabelAt(9));
232}
233
234TEST_F(RecentTabsSubMenuModelTest, MaxTabsPerSessionAndRecency) {
235  // Create a session: 2 windows with 5 tabs each.
236  RecentTabsBuilderTestHelper recent_tabs_builder;
237  recent_tabs_builder.AddSession();
238  for (int w = 0; w < 2; ++w) {
239    recent_tabs_builder.AddWindow(0);
240    for (int t = 0; t < 5; ++t)
241      recent_tabs_builder.AddTab(0, w);
242  }
243  recent_tabs_builder.RegisterRecentTabs(&associator_);
244
245  // Verify that data is populated correctly in RecentTabsSubMenuModel.
246  // Expected menu:
247  // - max tabs per session is 4, so only 4 most-recent tabs will show,
248  //   independent of which window they came from.
249  // Menu index  Menu items
250  // --------------------------------------
251  // 0           Reopen closed tab
252  // 1           <separator>
253  // 2           <section header for session>
254  // 3-6         <4 most-recent tabs of session>
255  // 7           <separator>
256  // 8           More...
257
258  TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
259  int num_items = model.GetItemCount();
260  EXPECT_EQ(9, num_items);
261
262  std::vector<string16> tab_titles =
263      recent_tabs_builder.GetTabTitlesSortedByRecency();
264  for (int i = 0; i < 4; ++i)
265    EXPECT_EQ(tab_titles[i], model.GetLabelAt(i + 3));
266}
267
268TEST_F(RecentTabsSubMenuModelTest, MaxWidth) {
269  // Create 1 session with 1 window and 1 tab.
270  RecentTabsBuilderTestHelper recent_tabs_builder;
271  recent_tabs_builder.AddSession();
272  recent_tabs_builder.AddWindow(0);
273  recent_tabs_builder.AddTab(0, 0);
274  recent_tabs_builder.RegisterRecentTabs(&associator_);
275
276  // Menu index  Menu items
277  // --------------------------------------
278  // 0           Reopen closed tab
279  // 1           <separator>
280  // 2           <section header for 1st session>
281  // 3           <the only tab of the only window of session 1>
282  // 4           <separator>
283  // 5           More...
284
285  TestRecentTabsSubMenuModel model(NULL, browser(), &associator_);
286  EXPECT_EQ(6, model.GetItemCount());
287  EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
288  EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
289  EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(2));
290  EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(3));
291}
292
293TEST_F(RecentTabsSubMenuModelTest, MaxWidthNoDevices) {
294  // Expected menu:
295  // Menu index  Menu items
296  // --------------------------------------
297  // 0           Reopen closed tab
298  // 1           <separator>
299  // 2           No tabs from other Devices
300
301  TestRecentTabsSubMenuModel model(NULL, browser(), NULL);
302  EXPECT_EQ(3, model.GetItemCount());
303  EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(0));
304  EXPECT_NE(-1, model.GetMaxWidthForItemAtIndex(1));
305  EXPECT_EQ(-1, model.GetMaxWidthForItemAtIndex(2));
306}
307