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 #import <Cocoa/Cocoa.h>
8 #include "base/memory/ref_counted_memory.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/app/chrome_command_ids.h"
14 #include "chrome/browser/common/cancelable_request.h"
15 #include "chrome/browser/sessions/persistent_tab_restore_service.h"
16 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
17 #include "chrome/browser/ui/cocoa/history_menu_bridge.h"
18 #include "chrome/test/base/testing_profile.h"
19 #include "components/favicon_base/favicon_types.h"
20 #include "components/sessions/serialized_navigation_entry_test_helper.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #import "testing/gtest_mac.h"
24 #include "third_party/skia/include/core/SkBitmap.h"
25 #include "ui/gfx/codec/png_codec.h"
29 class MockTRS : public PersistentTabRestoreService {
31 MockTRS(Profile* profile) : PersistentTabRestoreService(profile, NULL) {}
32 MOCK_CONST_METHOD0(entries, const TabRestoreService::Entries&());
35 class MockBridge : public HistoryMenuBridge {
37 MockBridge(Profile* profile)
38 : HistoryMenuBridge(profile),
39 menu_([[NSMenu alloc] initWithTitle:@"History"]) {}
41 virtual NSMenu* HistoryMenu() OVERRIDE {
46 base::scoped_nsobject<NSMenu> menu_;
49 class HistoryMenuBridgeTest : public CocoaProfileTest {
52 virtual void SetUp() {
53 CocoaProfileTest::SetUp();
54 profile()->CreateFaviconService();
55 bridge_.reset(new MockBridge(profile()));
58 // We are a friend of HistoryMenuBridge (and have access to
59 // protected methods), but none of the classes generated by TEST_F()
60 // are. Wraps common commands.
61 void ClearMenuSection(NSMenu* menu,
63 bridge_->ClearMenuSection(menu, tag);
66 void AddItemToBridgeMenu(HistoryMenuBridge::HistoryItem* item,
70 bridge_->AddItemToMenu(item, menu, tag, index);
73 NSMenuItem* AddItemToMenu(NSMenu* menu,
77 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
78 keyEquivalent:@""] autorelease];
81 [item setAction:selector];
82 [item setTarget:bridge_->controller_.get()];
88 HistoryMenuBridge::HistoryItem* CreateItem(const base::string16& title) {
89 HistoryMenuBridge::HistoryItem* item =
90 new HistoryMenuBridge::HistoryItem();
92 item->url = GURL(title);
96 MockTRS::Tab CreateSessionTab(const std::string& url,
97 const std::string& title) {
99 tab.current_navigation_index = 0;
100 tab.navigations.push_back(
101 sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
106 void GetFaviconForHistoryItem(HistoryMenuBridge::HistoryItem* item) {
107 bridge_->GetFaviconForHistoryItem(item);
110 void GotFaviconData(HistoryMenuBridge::HistoryItem* item,
111 const favicon_base::FaviconImageResult& image_result) {
112 bridge_->GotFaviconData(item, image_result);
115 CancelableRequestConsumerTSimple<HistoryMenuBridge::HistoryItem*>&
117 return bridge_->favicon_consumer_;
120 scoped_ptr<MockBridge> bridge_;
123 // Edge case test for clearing until the end of a menu.
124 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuUntilEnd) {
125 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
126 AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisitedTitle);
128 NSInteger tag = HistoryMenuBridge::kVisited;
129 AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
130 AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
131 AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
132 AddItemToMenu(menu, @"delta", @selector(openHistoryMenuItem:), tag);
134 ClearMenuSection(menu, HistoryMenuBridge::kVisited);
136 EXPECT_EQ(1, [menu numberOfItems]);
137 EXPECT_NSEQ(@"HEADER",
138 [[menu itemWithTag:HistoryMenuBridge::kVisitedTitle] title]);
141 // Skip menu items that are not hooked up to |-openHistoryMenuItem:|.
142 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuSkipping) {
143 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
144 AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisitedTitle);
146 NSInteger tag = HistoryMenuBridge::kVisited;
147 AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
148 AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
149 AddItemToMenu(menu, @"TITLE", NULL, HistoryMenuBridge::kRecentlyClosedTitle);
150 AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
152 ClearMenuSection(menu, tag);
154 EXPECT_EQ(2, [menu numberOfItems]);
155 EXPECT_NSEQ(@"HEADER",
156 [[menu itemWithTag:HistoryMenuBridge::kVisitedTitle] title]);
157 EXPECT_NSEQ(@"TITLE",
158 [[menu itemAtIndex:1] title]);
161 // Edge case test for clearing an empty menu.
162 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuEmpty) {
163 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
164 AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisited);
166 ClearMenuSection(menu, HistoryMenuBridge::kVisited);
168 EXPECT_EQ(1, [menu numberOfItems]);
169 EXPECT_NSEQ(@"HEADER",
170 [[menu itemWithTag:HistoryMenuBridge::kVisited] title]);
173 // Test that AddItemToMenu() properly adds HistoryItem objects as menus.
174 TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
175 NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
177 const base::string16 short_url = base::ASCIIToUTF16("http://foo/");
178 const base::string16 long_url = base::ASCIIToUTF16(
179 "http://super-duper-long-url--."
180 "that.cannot.possibly.fit.even-in-80-columns"
181 "or.be.reasonably-displayed-in-a-menu"
182 "without.looking-ridiculous.com/"); // 140 chars total
184 // HistoryItems are owned by the HistoryMenuBridge when AddItemToBridgeMenu()
185 // is called, which places them into the |menu_item_map_|, which owns them.
186 HistoryMenuBridge::HistoryItem* item1 = CreateItem(short_url);
187 AddItemToBridgeMenu(item1, menu, 100, 0);
189 HistoryMenuBridge::HistoryItem* item2 = CreateItem(long_url);
190 AddItemToBridgeMenu(item2, menu, 101, 1);
192 EXPECT_EQ(2, [menu numberOfItems]);
194 EXPECT_EQ(@selector(openHistoryMenuItem:), [[menu itemAtIndex:0] action]);
195 EXPECT_EQ(@selector(openHistoryMenuItem:), [[menu itemAtIndex:1] action]);
197 EXPECT_EQ(100, [[menu itemAtIndex:0] tag]);
198 EXPECT_EQ(101, [[menu itemAtIndex:1] tag]);
200 // Make sure a short title looks fine
201 NSString* s = [[menu itemAtIndex:0] title];
202 EXPECT_EQ(base::SysNSStringToUTF16(s), short_url);
204 // Make sure a super-long title gets trimmed
205 s = [[menu itemAtIndex:0] title];
206 EXPECT_TRUE([s length] < long_url.length());
208 // Confirm tooltips and confirm they are not trimmed (like the item
209 // name might be). Add tolerance for URL fixer-upping;
210 // e.g. http://foo becomes http://foo/)
211 EXPECT_GE([[[menu itemAtIndex:0] toolTip] length], (2*short_url.length()-5));
212 EXPECT_GE([[[menu itemAtIndex:1] toolTip] length], (2*long_url.length()-5));
215 // Test that the menu is created for a set of simple tabs.
216 TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabs) {
217 scoped_ptr<MockTRS> trs(new MockTRS(profile()));
218 MockTRS::Entries entries;
220 MockTRS::Tab tab1 = CreateSessionTab("http://google.com", "Google");
222 entries.push_back(&tab1);
224 MockTRS::Tab tab2 = CreateSessionTab("http://apple.com", "Apple");
226 entries.push_back(&tab2);
228 using ::testing::ReturnRef;
229 EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
231 bridge_->TabRestoreServiceChanged(trs.get());
233 NSMenu* menu = bridge_->HistoryMenu();
234 ASSERT_EQ(2U, [[menu itemArray] count]);
236 NSMenuItem* item1 = [menu itemAtIndex:0];
237 MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
239 EXPECT_EQ(24, hist1->session_id);
240 EXPECT_NSEQ(@"Google", [item1 title]);
242 NSMenuItem* item2 = [menu itemAtIndex:1];
243 MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
245 EXPECT_EQ(42, hist2->session_id);
246 EXPECT_NSEQ(@"Apple", [item2 title]);
249 // Test that the menu is created for a mix of windows and tabs.
250 TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabsAndWindows) {
251 scoped_ptr<MockTRS> trs(new MockTRS(profile()));
252 MockTRS::Entries entries;
254 MockTRS::Tab tab1 = CreateSessionTab("http://google.com", "Google");
256 entries.push_back(&tab1);
258 MockTRS::Window win1;
260 win1.tabs.push_back(CreateSessionTab("http://foo.com", "foo"));
261 win1.tabs[0].id = 31;
262 win1.tabs.push_back(CreateSessionTab("http://bar.com", "bar"));
263 win1.tabs[1].id = 32;
264 entries.push_back(&win1);
266 MockTRS::Tab tab2 = CreateSessionTab("http://apple.com", "Apple");
268 entries.push_back(&tab2);
270 MockTRS::Window win2;
272 win2.tabs.push_back(CreateSessionTab("http://magic.com", "magic"));
273 win2.tabs[0].id = 51;
274 win2.tabs.push_back(CreateSessionTab("http://goats.com", "goats"));
275 win2.tabs[1].id = 52;
276 win2.tabs.push_back(CreateSessionTab("http://teleporter.com", "teleporter"));
277 win2.tabs[1].id = 53;
278 entries.push_back(&win2);
280 using ::testing::ReturnRef;
281 EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
283 bridge_->TabRestoreServiceChanged(trs.get());
285 NSMenu* menu = bridge_->HistoryMenu();
286 ASSERT_EQ(4U, [[menu itemArray] count]);
288 NSMenuItem* item1 = [menu itemAtIndex:0];
289 MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
291 EXPECT_EQ(24, hist1->session_id);
292 EXPECT_NSEQ(@"Google", [item1 title]);
294 NSMenuItem* item2 = [menu itemAtIndex:1];
295 MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
297 EXPECT_EQ(30, hist2->session_id);
298 EXPECT_EQ(2U, hist2->tabs.size());
299 // Do not test menu item title because it is localized.
300 NSMenu* submenu1 = [item2 submenu];
301 EXPECT_EQ(4U, [[submenu1 itemArray] count]);
302 // Do not test Restore All Tabs because it is localiced.
303 EXPECT_TRUE([[submenu1 itemAtIndex:1] isSeparatorItem]);
304 EXPECT_NSEQ(@"foo", [[submenu1 itemAtIndex:2] title]);
305 EXPECT_NSEQ(@"bar", [[submenu1 itemAtIndex:3] title]);
307 NSMenuItem* item3 = [menu itemAtIndex:2];
308 MockBridge::HistoryItem* hist3 = bridge_->HistoryItemForMenuItem(item3);
310 EXPECT_EQ(42, hist3->session_id);
311 EXPECT_NSEQ(@"Apple", [item3 title]);
313 NSMenuItem* item4 = [menu itemAtIndex:3];
314 MockBridge::HistoryItem* hist4 = bridge_->HistoryItemForMenuItem(item4);
316 EXPECT_EQ(50, hist4->session_id);
317 EXPECT_EQ(3U, hist4->tabs.size());
318 // Do not test menu item title because it is localized.
319 NSMenu* submenu2 = [item4 submenu];
320 EXPECT_EQ(5U, [[submenu2 itemArray] count]);
321 // Do not test Restore All Tabs because it is localiced.
322 EXPECT_TRUE([[submenu2 itemAtIndex:1] isSeparatorItem]);
323 EXPECT_NSEQ(@"magic", [[submenu2 itemAtIndex:2] title]);
324 EXPECT_NSEQ(@"goats", [[submenu2 itemAtIndex:3] title]);
325 EXPECT_NSEQ(@"teleporter", [[submenu2 itemAtIndex:4] title]);
328 // Tests that we properly request an icon from the FaviconService.
329 TEST_F(HistoryMenuBridgeTest, GetFaviconForHistoryItem) {
330 // Create a fake item.
331 HistoryMenuBridge::HistoryItem item;
332 item.title = base::ASCIIToUTF16("Title");
333 item.url = GURL("http://google.com");
336 GetFaviconForHistoryItem(&item);
338 // Make sure the item was modified properly.
339 EXPECT_TRUE(item.icon_requested);
340 EXPECT_NE(base::CancelableTaskTracker::kBadTaskId, item.icon_task_id);
343 TEST_F(HistoryMenuBridgeTest, GotFaviconData) {
344 // Create a dummy bitmap.
346 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 25, 25);
347 bitmap.allocPixels();
348 bitmap.eraseARGB(255, 255, 0, 0);
350 // Set up the HistoryItem.
351 HistoryMenuBridge::HistoryItem item;
352 item.menu_item.reset([[NSMenuItem alloc] init]);
353 GetFaviconForHistoryItem(&item);
355 // Pretend to be called back.
356 favicon_base::FaviconImageResult image_result;
357 image_result.image = gfx::Image::CreateFrom1xBitmap(bitmap);
358 GotFaviconData(&item, image_result);
360 // Make sure the callback works.
361 EXPECT_FALSE(item.icon_requested);
362 EXPECT_TRUE(item.icon.get());
363 EXPECT_TRUE([item.menu_item image]);