blob: 77a58df3e7fd4b79ae629956fd9545f4109a8e11 [file] [log] [blame]
// Copyright (c) 2011 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.
#import <AppKit/AppKit.h>
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
#include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"
#include "ui/base/l10n/l10n_util.h"
using base::ASCIIToUTF16;
class TestBookmarkMenuBridge : public BookmarkMenuBridge {
public:
TestBookmarkMenuBridge(Profile* profile, NSMenu *menu)
: BookmarkMenuBridge(profile, menu),
menu_(menu) {
}
~TestBookmarkMenuBridge() override { [menu_ autorelease]; }
NSMenu* menu_;
protected:
// Overridden from BookmarkMenuBridge.
NSMenu* BookmarkMenu() override { return menu_; }
};
// TODO(jrg): see refactor comment in bookmark_bar_state_controller_unittest.mm
class BookmarkMenuBridgeTest : public CocoaProfileTest {
public:
virtual void SetUp() {
CocoaProfileTest::SetUp();
ASSERT_TRUE(profile());
NSMenu* menu = [[NSMenu alloc] initWithTitle:@"test"];
bridge_.reset(new TestBookmarkMenuBridge(profile(), menu));
EXPECT_TRUE(bridge_.get());
}
// We are a friend of BookmarkMenuBridge (and have access to
// protected methods), but none of the classes generated by TEST_F()
// are. This (and AddNodeToMenu()) are simple wrappers to let
// derived test classes have access to protected methods.
void ClearBookmarkMenu(BookmarkMenuBridge* bridge, NSMenu* menu) {
bridge->ClearBookmarkMenu(menu);
}
void InvalidateMenu() { bridge_->InvalidateMenu(); }
bool menu_is_valid() { return bridge_->menuIsValid_; }
void AddNodeToMenu(BookmarkMenuBridge* bridge,
const BookmarkNode* root,
NSMenu* menu) {
bridge->AddNodeToMenu(root, menu, true);
}
void AddItemToMenu(BookmarkMenuBridge* bridge,
int command_id,
int message_id,
const BookmarkNode* node,
NSMenu* menu,
bool enable) {
bridge->AddItemToMenu(command_id, message_id, node, menu, enable);
}
NSMenuItem* MenuItemForNode(BookmarkMenuBridge* bridge,
const BookmarkNode* node) {
return bridge->MenuItemForNode(node);
}
NSMenuItem* AddTestMenuItem(NSMenu *menu, NSString *title, SEL selector) {
NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
keyEquivalent:@""] autorelease];
if (selector)
[item setAction:selector];
[menu addItem:item];
return item;
}
scoped_ptr<TestBookmarkMenuBridge> bridge_;
};
TEST_F(BookmarkMenuBridgeTest, TestBookmarkMenuAutoSeparator) {
BookmarkModel* model = bridge_->GetBookmarkModel();
bridge_->BookmarkModelLoaded(model, false);
NSMenu* menu = bridge_->menu_;
bridge_->UpdateMenu(menu);
// The bare menu after loading used to have a separator and an
// "Other Bookmarks" submenu, but we no longer show those items if the
// "Other Bookmarks" submenu would be empty.
EXPECT_EQ(0, [menu numberOfItems]);
// Add a bookmark and reload and there should be 8 items: the previous
// menu contents plus two new separator, the new bookmark and three
// versions of 'Open All Bookmarks' menu items.
const BookmarkNode* parent = model->bookmark_bar_node();
const char* url = "http://www.zim-bop-a-dee.com/";
model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
bridge_->UpdateMenu(menu);
EXPECT_EQ(6, [menu numberOfItems]);
// Remove the new bookmark and reload and we should have 2 items again
// because the separator should have been removed as well.
model->Remove(parent, 0);
bridge_->UpdateMenu(menu);
EXPECT_EQ(0, [menu numberOfItems]);
}
// Test that ClearBookmarkMenu() removes all bookmark menus.
TEST_F(BookmarkMenuBridgeTest, TestClearBookmarkMenu) {
NSMenu* menu = bridge_->menu_;
AddTestMenuItem(menu, @"hi mom", nil);
AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
NSMenuItem* item = AddTestMenuItem(menu, @"hi mom", nil);
[item setSubmenu:[[[NSMenu alloc] initWithTitle:@"bar"] autorelease]];
AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
AddTestMenuItem(menu, @"zippy", @selector(length));
[menu addItem:[NSMenuItem separatorItem]];
ClearBookmarkMenu(bridge_.get(), menu);
// Make sure all bookmark items are removed, all items with
// submenus removed, and all separator items are gone.
EXPECT_EQ(2, [menu numberOfItems]);
for (NSMenuItem *item in [menu itemArray]) {
EXPECT_NSNE(@"not", [item title]);
}
}
// Test invalidation
TEST_F(BookmarkMenuBridgeTest, TestInvalidation) {
BookmarkModel* model = bridge_->GetBookmarkModel();
bridge_->BookmarkModelLoaded(model, false);
EXPECT_FALSE(menu_is_valid());
bridge_->UpdateMenu(bridge_->menu_);
EXPECT_TRUE(menu_is_valid());
InvalidateMenu();
EXPECT_FALSE(menu_is_valid());
InvalidateMenu();
EXPECT_FALSE(menu_is_valid());
bridge_->UpdateMenu(bridge_->menu_);
EXPECT_TRUE(menu_is_valid());
bridge_->UpdateMenu(bridge_->menu_);
EXPECT_TRUE(menu_is_valid());
const BookmarkNode* parent = model->bookmark_bar_node();
const char* url = "http://www.zim-bop-a-dee.com/";
model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
EXPECT_FALSE(menu_is_valid());
bridge_->UpdateMenu(bridge_->menu_);
EXPECT_TRUE(menu_is_valid());
}
// Test that AddNodeToMenu() properly adds bookmark nodes as menus,
// including the recursive case.
TEST_F(BookmarkMenuBridgeTest, TestAddNodeToMenu) {
base::string16 empty;
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* root = model->bookmark_bar_node();
EXPECT_TRUE(model && root);
const char* short_url = "http://foo/";
const char* long_url = "http://super-duper-long-url--."
"that.cannot.possibly.fit.even-in-80-columns"
"or.be.reasonably-displayed-in-a-menu"
"without.looking-ridiculous.com/"; // 140 chars total
// 3 nodes; middle one has a child, last one has a HUGE URL
// Set their titles to be the same as the URLs
const BookmarkNode* node = NULL;
model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
bridge_->UpdateMenu(menu);
int prev_count = [menu numberOfItems] - 1; // "extras" added at this point
node = model->AddFolder(root, 1, empty);
model->AddURL(root, 2, ASCIIToUTF16(long_url), GURL(long_url));
// And the submenu fo the middle one
model->AddURL(node, 0, empty, GURL("http://sub"));
bridge_->UpdateMenu(menu);
EXPECT_EQ((NSInteger)(prev_count+3), [menu numberOfItems]);
// Verify the 1st one is there with the right action.
NSMenuItem* item = [menu itemWithTitle:[NSString
stringWithUTF8String:short_url]];
EXPECT_TRUE(item);
EXPECT_EQ(@selector(openBookmarkMenuItem:), [item action]);
EXPECT_EQ(NO, [item hasSubmenu]);
NSMenuItem* short_item = item;
NSMenuItem* long_item = nil;
// Now confirm we have 1 submenu (the one we added, and not "other")
int subs = 0;
for (item in [menu itemArray]) {
if ([item hasSubmenu])
subs++;
}
EXPECT_EQ(1, subs);
for (item in [menu itemArray]) {
if ([[item title] hasPrefix:@"http://super-duper"]) {
long_item = item;
break;
}
}
EXPECT_TRUE(long_item);
// Make sure a short title looks fine
NSString* s = [short_item title];
EXPECT_NSEQ([NSString stringWithUTF8String:short_url], s);
// Make sure a super-long title gets trimmed
s = [long_item title];
EXPECT_TRUE([s length] < strlen(long_url));
// Confirm tooltips and confirm they are not trimmed (like the item
// name might be). Add tolerance for URL fixer-upping;
// e.g. http://foo becomes http://foo/)
EXPECT_GE([[short_item toolTip] length], strlen(short_url) - 3);
EXPECT_GE([[long_item toolTip] length], strlen(long_url) - 3);
// Make sure the favicon is non-nil (should be either the default site
// icon or a favicon, if present).
EXPECT_TRUE([short_item image]);
EXPECT_TRUE([long_item image]);
}
// Test that AddItemToMenu() properly added versions of
// 'Open All Bookmarks' as menu items.
TEST_F(BookmarkMenuBridgeTest, TestAddItemToMenu) {
NSString* title;
NSMenuItem* item;
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* root = model->bookmark_bar_node();
EXPECT_TRUE(model && root);
EXPECT_EQ(0, [menu numberOfItems]);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, true);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, true);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, true);
EXPECT_EQ(3, [menu numberOfItems]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(@selector(openAllBookmarks:), [item action]);
EXPECT_TRUE([item isEnabled]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(@selector(openAllBookmarksNewWindow:), [item action]);
EXPECT_TRUE([item isEnabled]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(@selector(openAllBookmarksIncognitoWindow:), [item action]);
EXPECT_TRUE([item isEnabled]);
ClearBookmarkMenu(bridge_.get(), menu);
EXPECT_EQ(0, [menu numberOfItems]);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, false);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, false);
AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, false);
EXPECT_EQ(3, [menu numberOfItems]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(nil, [item action]);
EXPECT_FALSE([item isEnabled]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(nil, [item action]);
EXPECT_FALSE([item isEnabled]);
title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
item = [menu itemWithTitle:title];
EXPECT_TRUE(item);
EXPECT_EQ(nil, [item action]);
EXPECT_FALSE([item isEnabled]);
}
// Makes sure our internal map of BookmarkNode to NSMenuItem works.
TEST_F(BookmarkMenuBridgeTest, TestGetMenuItemForNode) {
base::string16 empty;
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
const BookmarkNode* root = model->AddFolder(bookmark_bar, 0, empty);
EXPECT_TRUE(model && root);
model->AddURL(root, 0, ASCIIToUTF16("Test Item"), GURL("http://test"));
AddNodeToMenu(bridge_.get(), root, menu);
EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
model->AddURL(root, 1, ASCIIToUTF16("Test 2"), GURL("http://second-test"));
AddNodeToMenu(bridge_.get(), root, menu);
EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(1)));
const BookmarkNode* removed_node = root->GetChild(0);
EXPECT_EQ(2, root->child_count());
model->Remove(root, 0);
EXPECT_EQ(1, root->child_count());
bridge_->UpdateMenu(menu);
EXPECT_FALSE(MenuItemForNode(bridge_.get(), removed_node));
EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
const BookmarkNode empty_node(GURL("http://no-where/"));
EXPECT_FALSE(MenuItemForNode(bridge_.get(), &empty_node));
EXPECT_FALSE(MenuItemForNode(bridge_.get(), NULL));
}
// Test that Loaded() adds both the bookmark bar nodes and the "other" nodes.
TEST_F(BookmarkMenuBridgeTest, TestAddNodeToOther) {
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* root = model->other_node();
EXPECT_TRUE(model && root);
const char* short_url = "http://foo/";
model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
bridge_->UpdateMenu(menu);
ASSERT_GT([menu numberOfItems], 0);
NSMenuItem* other = [menu itemAtIndex:([menu numberOfItems]-1)];
EXPECT_TRUE(other);
EXPECT_TRUE([other hasSubmenu]);
ASSERT_GT([[other submenu] numberOfItems], 0);
EXPECT_NSEQ(@"http://foo/", [[[other submenu] itemAtIndex:0] title]);
}
TEST_F(BookmarkMenuBridgeTest, TestFaviconLoading) {
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* root = model->bookmark_bar_node();
EXPECT_TRUE(model && root);
const BookmarkNode* node =
model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
GURL("http://favicon-test"));
bridge_->UpdateMenu(menu);
NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
EXPECT_TRUE([item image]);
[item setImage:nil];
bridge_->BookmarkNodeFaviconChanged(model, node);
EXPECT_TRUE([item image]);
}
TEST_F(BookmarkMenuBridgeTest, TestChangeTitle) {
NSMenu* menu = bridge_->menu_;
BookmarkModel* model = bridge_->GetBookmarkModel();
const BookmarkNode* root = model->bookmark_bar_node();
EXPECT_TRUE(model && root);
const BookmarkNode* node =
model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
GURL("http://title-test"));
bridge_->UpdateMenu(menu);
NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
EXPECT_TRUE([item image]);
model->SetTitle(node, ASCIIToUTF16("New Title"));
item = [menu itemWithTitle:@"Test Item"];
EXPECT_FALSE(item);
item = [menu itemWithTitle:@"New Title"];
EXPECT_TRUE(item);
}