Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2011 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 #import <Cocoa/Cocoa.h>
      6 #include <vector>
      7 
      8 #include "base/memory/ref_counted_memory.h"
      9 #include "base/string_util.h"
     10 #include "base/sys_string_conversions.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/app/chrome_command_ids.h"
     13 #include "chrome/browser/sessions/tab_restore_service.h"
     14 #include "chrome/browser/ui/cocoa/browser_test_helper.h"
     15 #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
     16 #include "chrome/browser/ui/cocoa/history_menu_bridge.h"
     17 #include "content/browser/cancelable_request.h"
     18 #include "testing/gmock/include/gmock/gmock.h"
     19 #include "testing/gtest/include/gtest/gtest.h"
     20 #import "testing/gtest_mac.h"
     21 #include "ui/gfx/codec/png_codec.h"
     22 
     23 namespace {
     24 
     25 class MockTRS : public TabRestoreService {
     26  public:
     27   MockTRS(Profile* profile) : TabRestoreService(profile, NULL) {}
     28   MOCK_CONST_METHOD0(entries, const TabRestoreService::Entries&());
     29 };
     30 
     31 class MockBridge : public HistoryMenuBridge {
     32  public:
     33   MockBridge(Profile* profile)
     34       : HistoryMenuBridge(profile),
     35         menu_([[NSMenu alloc] initWithTitle:@"History"]) {}
     36 
     37   virtual NSMenu* HistoryMenu() {
     38     return menu_.get();
     39   }
     40 
     41  private:
     42   scoped_nsobject<NSMenu> menu_;
     43 };
     44 
     45 class HistoryMenuBridgeTest : public CocoaTest {
     46  public:
     47 
     48   virtual void SetUp() {
     49     CocoaTest::SetUp();
     50     browser_test_helper_.profile()->CreateFaviconService();
     51     bridge_.reset(new MockBridge(browser_test_helper_.profile()));
     52   }
     53 
     54   // We are a friend of HistoryMenuBridge (and have access to
     55   // protected methods), but none of the classes generated by TEST_F()
     56   // are. Wraps common commands.
     57   void ClearMenuSection(NSMenu* menu,
     58                         NSInteger tag) {
     59     bridge_->ClearMenuSection(menu, tag);
     60   }
     61 
     62   void AddItemToBridgeMenu(HistoryMenuBridge::HistoryItem* item,
     63                            NSMenu* menu,
     64                            NSInteger tag,
     65                            NSInteger index) {
     66     bridge_->AddItemToMenu(item, menu, tag, index);
     67   }
     68 
     69   NSMenuItem* AddItemToMenu(NSMenu* menu,
     70                             NSString* title,
     71                             SEL selector,
     72                             int tag) {
     73     NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
     74                                             keyEquivalent:@""] autorelease];
     75     [item setTag:tag];
     76     if (selector) {
     77       [item setAction:selector];
     78       [item setTarget:bridge_->controller_.get()];
     79     }
     80     [menu addItem:item];
     81     return item;
     82   }
     83 
     84   HistoryMenuBridge::HistoryItem* CreateItem(const string16& title) {
     85     HistoryMenuBridge::HistoryItem* item =
     86         new HistoryMenuBridge::HistoryItem();
     87     item->title = title;
     88     item->url = GURL(title);
     89     return item;
     90   }
     91 
     92   MockTRS::Tab CreateSessionTab(const GURL& url, const string16& title) {
     93     MockTRS::Tab tab;
     94     tab.current_navigation_index = 0;
     95     tab.navigations.push_back(
     96         TabNavigation(0, url, GURL(), title, std::string(),
     97                       PageTransition::LINK));
     98     return tab;
     99   }
    100 
    101   void GetFaviconForHistoryItem(HistoryMenuBridge::HistoryItem* item) {
    102     bridge_->GetFaviconForHistoryItem(item);
    103   }
    104 
    105   void GotFaviconData(FaviconService::Handle handle,
    106                       history::FaviconData favicon) {
    107     bridge_->GotFaviconData(handle, favicon);
    108   }
    109 
    110   CancelableRequestConsumerTSimple<HistoryMenuBridge::HistoryItem*>&
    111       favicon_consumer() {
    112     return bridge_->favicon_consumer_;
    113   }
    114 
    115   BrowserTestHelper browser_test_helper_;
    116   scoped_ptr<MockBridge> bridge_;
    117 };
    118 
    119 // Edge case test for clearing until the end of a menu.
    120 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuUntilEnd) {
    121   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
    122   AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisitedTitle);
    123 
    124   NSInteger tag = HistoryMenuBridge::kMostVisited;
    125   AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
    126   AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
    127   AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
    128   AddItemToMenu(menu, @"delta", @selector(openHistoryMenuItem:), tag);
    129 
    130   ClearMenuSection(menu, HistoryMenuBridge::kMostVisited);
    131 
    132   EXPECT_EQ(1, [menu numberOfItems]);
    133   EXPECT_NSEQ(@"HEADER",
    134       [[menu itemWithTag:HistoryMenuBridge::kMostVisitedTitle] title]);
    135 }
    136 
    137 // Skip menu items that are not hooked up to |-openHistoryMenuItem:|.
    138 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuSkipping) {
    139   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
    140   AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisitedTitle);
    141 
    142   NSInteger tag = HistoryMenuBridge::kMostVisited;
    143   AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
    144   AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
    145   AddItemToMenu(menu, @"TITLE", NULL, HistoryMenuBridge::kRecentlyClosedTitle);
    146   AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
    147 
    148   ClearMenuSection(menu, tag);
    149 
    150   EXPECT_EQ(2, [menu numberOfItems]);
    151   EXPECT_NSEQ(@"HEADER",
    152       [[menu itemWithTag:HistoryMenuBridge::kMostVisitedTitle] title]);
    153   EXPECT_NSEQ(@"TITLE",
    154       [[menu itemAtIndex:1] title]);
    155 }
    156 
    157 // Edge case test for clearing an empty menu.
    158 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuEmpty) {
    159   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
    160   AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisited);
    161 
    162   ClearMenuSection(menu, HistoryMenuBridge::kMostVisited);
    163 
    164   EXPECT_EQ(1, [menu numberOfItems]);
    165   EXPECT_NSEQ(@"HEADER",
    166       [[menu itemWithTag:HistoryMenuBridge::kMostVisited] title]);
    167 }
    168 
    169 // Test that AddItemToMenu() properly adds HistoryItem objects as menus.
    170 TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
    171   NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
    172 
    173   const string16 short_url = ASCIIToUTF16("http://foo/");
    174   const string16 long_url = ASCIIToUTF16("http://super-duper-long-url--."
    175       "that.cannot.possibly.fit.even-in-80-columns"
    176       "or.be.reasonably-displayed-in-a-menu"
    177       "without.looking-ridiculous.com/"); // 140 chars total
    178 
    179   // HistoryItems are owned by the HistoryMenuBridge when AddItemToBridgeMenu()
    180   // is called, which places them into the |menu_item_map_|, which owns them.
    181   HistoryMenuBridge::HistoryItem* item1 = CreateItem(short_url);
    182   AddItemToBridgeMenu(item1, menu, 100, 0);
    183 
    184   HistoryMenuBridge::HistoryItem* item2 = CreateItem(long_url);
    185   AddItemToBridgeMenu(item2, menu, 101, 1);
    186 
    187   EXPECT_EQ(2, [menu numberOfItems]);
    188 
    189   EXPECT_EQ(@selector(openHistoryMenuItem:), [[menu itemAtIndex:0] action]);
    190   EXPECT_EQ(@selector(openHistoryMenuItem:), [[menu itemAtIndex:1] action]);
    191 
    192   EXPECT_EQ(100, [[menu itemAtIndex:0] tag]);
    193   EXPECT_EQ(101, [[menu itemAtIndex:1] tag]);
    194 
    195   // Make sure a short title looks fine
    196   NSString* s = [[menu itemAtIndex:0] title];
    197   EXPECT_EQ(base::SysNSStringToUTF16(s), short_url);
    198 
    199   // Make sure a super-long title gets trimmed
    200   s = [[menu itemAtIndex:0] title];
    201   EXPECT_TRUE([s length] < long_url.length());
    202 
    203   // Confirm tooltips and confirm they are not trimmed (like the item
    204   // name might be).  Add tolerance for URL fixer-upping;
    205   // e.g. http://foo becomes http://foo/)
    206   EXPECT_GE([[[menu itemAtIndex:0] toolTip] length], (2*short_url.length()-5));
    207   EXPECT_GE([[[menu itemAtIndex:1] toolTip] length], (2*long_url.length()-5));
    208 }
    209 
    210 // Test that the menu is created for a set of simple tabs.
    211 TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabs) {
    212   scoped_refptr<MockTRS> trs(new MockTRS(browser_test_helper_.profile()));
    213   MockTRS::Entries entries;
    214 
    215   MockTRS::Tab tab1 = CreateSessionTab(GURL("http://google.com"),
    216                                        ASCIIToUTF16("Google"));
    217   tab1.id = 24;
    218   entries.push_back(&tab1);
    219 
    220   MockTRS::Tab tab2 = CreateSessionTab(GURL("http://apple.com"),
    221                                        ASCIIToUTF16("Apple"));
    222   tab2.id = 42;
    223   entries.push_back(&tab2);
    224 
    225   using ::testing::ReturnRef;
    226   EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
    227 
    228   bridge_->TabRestoreServiceChanged(trs.get());
    229 
    230   NSMenu* menu = bridge_->HistoryMenu();
    231   ASSERT_EQ(2U, [[menu itemArray] count]);
    232 
    233   NSMenuItem* item1 = [menu itemAtIndex:0];
    234   MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
    235   EXPECT_TRUE(hist1);
    236   EXPECT_EQ(24, hist1->session_id);
    237   EXPECT_NSEQ(@"Google", [item1 title]);
    238 
    239   NSMenuItem* item2 = [menu itemAtIndex:1];
    240   MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
    241   EXPECT_TRUE(hist2);
    242   EXPECT_EQ(42, hist2->session_id);
    243   EXPECT_NSEQ(@"Apple", [item2 title]);
    244 }
    245 
    246 // Test that the menu is created for a mix of windows and tabs.
    247 TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabsAndWindows) {
    248   scoped_refptr<MockTRS> trs(new MockTRS(browser_test_helper_.profile()));
    249   MockTRS::Entries entries;
    250 
    251   MockTRS::Tab tab1 = CreateSessionTab(GURL("http://google.com"),
    252                                        ASCIIToUTF16("Google"));
    253   tab1.id = 24;
    254   entries.push_back(&tab1);
    255 
    256   MockTRS::Window win1;
    257   win1.id = 30;
    258   win1.tabs.push_back(
    259       CreateSessionTab(GURL("http://foo.com"), ASCIIToUTF16("foo")));
    260   win1.tabs[0].id = 31;
    261   win1.tabs.push_back(
    262       CreateSessionTab(GURL("http://bar.com"), ASCIIToUTF16("bar")));
    263   win1.tabs[1].id = 32;
    264   entries.push_back(&win1);
    265 
    266   MockTRS::Tab tab2 = CreateSessionTab(GURL("http://apple.com"),
    267                                        ASCIIToUTF16("Apple"));
    268   tab2.id = 42;
    269   entries.push_back(&tab2);
    270 
    271   MockTRS::Window win2;
    272   win2.id = 50;
    273   win2.tabs.push_back(
    274       CreateSessionTab(GURL("http://magic.com"), ASCIIToUTF16("magic")));
    275   win2.tabs[0].id = 51;
    276   win2.tabs.push_back(
    277       CreateSessionTab(GURL("http://goats.com"), ASCIIToUTF16("goats")));
    278   win2.tabs[1].id = 52;
    279   win2.tabs.push_back(
    280       CreateSessionTab(GURL("http://teleporter.com"),
    281                        ASCIIToUTF16("teleporter")));
    282   win2.tabs[1].id = 53;
    283   entries.push_back(&win2);
    284 
    285   using ::testing::ReturnRef;
    286   EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
    287 
    288   bridge_->TabRestoreServiceChanged(trs.get());
    289 
    290   NSMenu* menu = bridge_->HistoryMenu();
    291   ASSERT_EQ(4U, [[menu itemArray] count]);
    292 
    293   NSMenuItem* item1 = [menu itemAtIndex:0];
    294   MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
    295   EXPECT_TRUE(hist1);
    296   EXPECT_EQ(24, hist1->session_id);
    297   EXPECT_NSEQ(@"Google", [item1 title]);
    298 
    299   NSMenuItem* item2 = [menu itemAtIndex:1];
    300   MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
    301   EXPECT_TRUE(hist2);
    302   EXPECT_EQ(30, hist2->session_id);
    303   EXPECT_EQ(2U, hist2->tabs.size());
    304   // Do not test menu item title because it is localized.
    305   NSMenu* submenu1 = [item2 submenu];
    306   EXPECT_EQ(4U, [[submenu1 itemArray] count]);
    307   // Do not test Restore All Tabs because it is localiced.
    308   EXPECT_TRUE([[submenu1 itemAtIndex:1] isSeparatorItem]);
    309   EXPECT_NSEQ(@"foo", [[submenu1 itemAtIndex:2] title]);
    310   EXPECT_NSEQ(@"bar", [[submenu1 itemAtIndex:3] title]);
    311 
    312   NSMenuItem* item3 = [menu itemAtIndex:2];
    313   MockBridge::HistoryItem* hist3 = bridge_->HistoryItemForMenuItem(item3);
    314   EXPECT_TRUE(hist3);
    315   EXPECT_EQ(42, hist3->session_id);
    316   EXPECT_NSEQ(@"Apple", [item3 title]);
    317 
    318   NSMenuItem* item4 = [menu itemAtIndex:3];
    319   MockBridge::HistoryItem* hist4 = bridge_->HistoryItemForMenuItem(item4);
    320   EXPECT_TRUE(hist4);
    321   EXPECT_EQ(50, hist4->session_id);
    322   EXPECT_EQ(3U, hist4->tabs.size());
    323   // Do not test menu item title because it is localized.
    324   NSMenu* submenu2 = [item4 submenu];
    325   EXPECT_EQ(5U, [[submenu2 itemArray] count]);
    326   // Do not test Restore All Tabs because it is localiced.
    327   EXPECT_TRUE([[submenu2 itemAtIndex:1] isSeparatorItem]);
    328   EXPECT_NSEQ(@"magic", [[submenu2 itemAtIndex:2] title]);
    329   EXPECT_NSEQ(@"goats", [[submenu2 itemAtIndex:3] title]);
    330   EXPECT_NSEQ(@"teleporter", [[submenu2 itemAtIndex:4] title]);
    331 }
    332 
    333 // Tests that we properly request an icon from the FaviconService.
    334 TEST_F(HistoryMenuBridgeTest, GetFaviconForHistoryItem) {
    335   // Create a fake item.
    336   HistoryMenuBridge::HistoryItem item;
    337   item.title = ASCIIToUTF16("Title");
    338   item.url = GURL("http://google.com");
    339 
    340   // Request the icon.
    341   GetFaviconForHistoryItem(&item);
    342 
    343   // Make sure that there is ClientData for the request.
    344   std::vector<HistoryMenuBridge::HistoryItem*> data;
    345   favicon_consumer().GetAllClientData(&data);
    346   ASSERT_EQ(data.size(), 1U);
    347   EXPECT_EQ(&item, data[0]);
    348 
    349   // Make sure the item was modified properly.
    350   EXPECT_TRUE(item.icon_requested);
    351   EXPECT_GT(item.icon_handle, 0);
    352 }
    353 
    354 TEST_F(HistoryMenuBridgeTest, GotFaviconData) {
    355   // Create a dummy bitmap.
    356   SkBitmap bitmap;
    357   bitmap.setConfig(SkBitmap::kARGB_8888_Config, 25, 25);
    358   bitmap.allocPixels();
    359   bitmap.eraseRGB(255, 0, 0);
    360 
    361   // Convert it to raw PNG bytes. We totally ignore color order here because
    362   // we just want to test the roundtrip through the Bridge, not that we can
    363   // make icons look pretty.
    364   std::vector<unsigned char> raw;
    365   gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &raw);
    366   scoped_refptr<RefCountedBytes> bytes();
    367 
    368   // Set up the HistoryItem.
    369   HistoryMenuBridge::HistoryItem item;
    370   item.menu_item.reset([[NSMenuItem alloc] init]);
    371   GetFaviconForHistoryItem(&item);
    372 
    373   // Pretend to be called back.
    374   history::FaviconData favicon;
    375   favicon.known_icon = true;
    376   favicon.image_data = new RefCountedBytes(raw);
    377   favicon.expired = false;
    378   favicon.icon_url = GURL();
    379   favicon.icon_type = history::FAVICON;
    380   GotFaviconData(item.icon_handle, favicon);
    381 
    382   // Make sure the callback works.
    383   EXPECT_FALSE(item.icon_requested);
    384   EXPECT_TRUE(item.icon.get());
    385   EXPECT_TRUE([item.menu_item image]);
    386 }
    387 
    388 }  // namespace
    389