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.
5 #import <AppKit/AppKit.h>
7 #include "base/strings/string16.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
12 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
13 #include "chrome/test/base/testing_profile.h"
14 #include "components/bookmarks/browser/bookmark_model.h"
15 #include "grit/generated_resources.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #import "testing/gtest_mac.h"
18 #include "testing/platform_test.h"
19 #include "ui/base/l10n/l10n_util.h"
21 using base::ASCIIToUTF16;
23 class TestBookmarkMenuBridge : public BookmarkMenuBridge {
25 TestBookmarkMenuBridge(Profile* profile, NSMenu *menu)
26 : BookmarkMenuBridge(profile, menu),
29 virtual ~TestBookmarkMenuBridge() {
36 // Overridden from BookmarkMenuBridge.
37 virtual NSMenu* BookmarkMenu() OVERRIDE {
42 // TODO(jrg): see refactor comment in bookmark_bar_state_controller_unittest.mm
43 class BookmarkMenuBridgeTest : public CocoaProfileTest {
46 virtual void SetUp() {
47 CocoaProfileTest::SetUp();
48 ASSERT_TRUE(profile());
50 NSMenu* menu = [[NSMenu alloc] initWithTitle:@"test"];
51 bridge_.reset(new TestBookmarkMenuBridge(profile(), menu));
52 EXPECT_TRUE(bridge_.get());
55 // We are a friend of BookmarkMenuBridge (and have access to
56 // protected methods), but none of the classes generated by TEST_F()
57 // are. This (and AddNodeToMenu()) are simple wrappers to let
58 // derived test classes have access to protected methods.
59 void ClearBookmarkMenu(BookmarkMenuBridge* bridge, NSMenu* menu) {
60 bridge->ClearBookmarkMenu(menu);
63 void InvalidateMenu() { bridge_->InvalidateMenu(); }
64 bool menu_is_valid() { return bridge_->menuIsValid_; }
66 void AddNodeToMenu(BookmarkMenuBridge* bridge,
67 const BookmarkNode* root,
69 bridge->AddNodeToMenu(root, menu, true);
72 void AddItemToMenu(BookmarkMenuBridge* bridge,
75 const BookmarkNode* node,
78 bridge->AddItemToMenu(command_id, message_id, node, menu, enable);
81 NSMenuItem* MenuItemForNode(BookmarkMenuBridge* bridge,
82 const BookmarkNode* node) {
83 return bridge->MenuItemForNode(node);
86 NSMenuItem* AddTestMenuItem(NSMenu *menu, NSString *title, SEL selector) {
87 NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
88 keyEquivalent:@""] autorelease];
90 [item setAction:selector];
94 scoped_ptr<TestBookmarkMenuBridge> bridge_;
97 TEST_F(BookmarkMenuBridgeTest, TestBookmarkMenuAutoSeparator) {
98 BookmarkModel* model = bridge_->GetBookmarkModel();
99 bridge_->BookmarkModelLoaded(model, false);
100 NSMenu* menu = bridge_->menu_;
101 bridge_->UpdateMenu(menu);
102 // The bare menu after loading used to have a separator and an
103 // "Other Bookmarks" submenu, but we no longer show those items if the
104 // "Other Bookmarks" submenu would be empty.
105 EXPECT_EQ(0, [menu numberOfItems]);
106 // Add a bookmark and reload and there should be 8 items: the previous
107 // menu contents plus two new separator, the new bookmark and three
108 // versions of 'Open All Bookmarks' menu items.
109 const BookmarkNode* parent = model->bookmark_bar_node();
110 const char* url = "http://www.zim-bop-a-dee.com/";
111 model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
112 bridge_->UpdateMenu(menu);
113 EXPECT_EQ(6, [menu numberOfItems]);
114 // Remove the new bookmark and reload and we should have 2 items again
115 // because the separator should have been removed as well.
116 model->Remove(parent, 0);
117 bridge_->UpdateMenu(menu);
118 EXPECT_EQ(0, [menu numberOfItems]);
121 // Test that ClearBookmarkMenu() removes all bookmark menus.
122 TEST_F(BookmarkMenuBridgeTest, TestClearBookmarkMenu) {
123 NSMenu* menu = bridge_->menu_;
125 AddTestMenuItem(menu, @"hi mom", nil);
126 AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
127 NSMenuItem* item = AddTestMenuItem(menu, @"hi mom", nil);
128 [item setSubmenu:[[[NSMenu alloc] initWithTitle:@"bar"] autorelease]];
129 AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
130 AddTestMenuItem(menu, @"zippy", @selector(length));
131 [menu addItem:[NSMenuItem separatorItem]];
133 ClearBookmarkMenu(bridge_.get(), menu);
135 // Make sure all bookmark items are removed, all items with
136 // submenus removed, and all separator items are gone.
137 EXPECT_EQ(2, [menu numberOfItems]);
138 for (NSMenuItem *item in [menu itemArray]) {
139 EXPECT_NSNE(@"not", [item title]);
144 TEST_F(BookmarkMenuBridgeTest, TestInvalidation) {
145 BookmarkModel* model = bridge_->GetBookmarkModel();
146 bridge_->BookmarkModelLoaded(model, false);
148 EXPECT_FALSE(menu_is_valid());
149 bridge_->UpdateMenu(bridge_->menu_);
150 EXPECT_TRUE(menu_is_valid());
153 EXPECT_FALSE(menu_is_valid());
155 EXPECT_FALSE(menu_is_valid());
156 bridge_->UpdateMenu(bridge_->menu_);
157 EXPECT_TRUE(menu_is_valid());
158 bridge_->UpdateMenu(bridge_->menu_);
159 EXPECT_TRUE(menu_is_valid());
161 const BookmarkNode* parent = model->bookmark_bar_node();
162 const char* url = "http://www.zim-bop-a-dee.com/";
163 model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
165 EXPECT_FALSE(menu_is_valid());
166 bridge_->UpdateMenu(bridge_->menu_);
167 EXPECT_TRUE(menu_is_valid());
170 // Test that AddNodeToMenu() properly adds bookmark nodes as menus,
171 // including the recursive case.
172 TEST_F(BookmarkMenuBridgeTest, TestAddNodeToMenu) {
173 base::string16 empty;
174 NSMenu* menu = bridge_->menu_;
176 BookmarkModel* model = bridge_->GetBookmarkModel();
177 const BookmarkNode* root = model->bookmark_bar_node();
178 EXPECT_TRUE(model && root);
180 const char* short_url = "http://foo/";
181 const char* long_url = "http://super-duper-long-url--."
182 "that.cannot.possibly.fit.even-in-80-columns"
183 "or.be.reasonably-displayed-in-a-menu"
184 "without.looking-ridiculous.com/"; // 140 chars total
186 // 3 nodes; middle one has a child, last one has a HUGE URL
187 // Set their titles to be the same as the URLs
188 const BookmarkNode* node = NULL;
189 model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
190 bridge_->UpdateMenu(menu);
191 int prev_count = [menu numberOfItems] - 1; // "extras" added at this point
192 node = model->AddFolder(root, 1, empty);
193 model->AddURL(root, 2, ASCIIToUTF16(long_url), GURL(long_url));
195 // And the submenu fo the middle one
196 model->AddURL(node, 0, empty, GURL("http://sub"));
197 bridge_->UpdateMenu(menu);
199 EXPECT_EQ((NSInteger)(prev_count+3), [menu numberOfItems]);
201 // Verify the 1st one is there with the right action.
202 NSMenuItem* item = [menu itemWithTitle:[NSString
203 stringWithUTF8String:short_url]];
205 EXPECT_EQ(@selector(openBookmarkMenuItem:), [item action]);
206 EXPECT_EQ(NO, [item hasSubmenu]);
207 NSMenuItem* short_item = item;
208 NSMenuItem* long_item = nil;
210 // Now confirm we have 1 submenu (the one we added, and not "other")
212 for (item in [menu itemArray]) {
213 if ([item hasSubmenu])
218 for (item in [menu itemArray]) {
219 if ([[item title] hasPrefix:@"http://super-duper"]) {
224 EXPECT_TRUE(long_item);
226 // Make sure a short title looks fine
227 NSString* s = [short_item title];
228 EXPECT_NSEQ([NSString stringWithUTF8String:short_url], s);
230 // Make sure a super-long title gets trimmed
231 s = [long_item title];
232 EXPECT_TRUE([s length] < strlen(long_url));
234 // Confirm tooltips and confirm they are not trimmed (like the item
235 // name might be). Add tolerance for URL fixer-upping;
236 // e.g. http://foo becomes http://foo/)
237 EXPECT_GE([[short_item toolTip] length], strlen(short_url) - 3);
238 EXPECT_GE([[long_item toolTip] length], strlen(long_url) - 3);
240 // Make sure the favicon is non-nil (should be either the default site
241 // icon or a favicon, if present).
242 EXPECT_TRUE([short_item image]);
243 EXPECT_TRUE([long_item image]);
246 // Test that AddItemToMenu() properly added versions of
247 // 'Open All Bookmarks' as menu items.
248 TEST_F(BookmarkMenuBridgeTest, TestAddItemToMenu) {
251 NSMenu* menu = bridge_->menu_;
253 BookmarkModel* model = bridge_->GetBookmarkModel();
254 const BookmarkNode* root = model->bookmark_bar_node();
255 EXPECT_TRUE(model && root);
256 EXPECT_EQ(0, [menu numberOfItems]);
258 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
259 IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, true);
260 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
261 IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, true);
262 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
263 IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, true);
264 EXPECT_EQ(3, [menu numberOfItems]);
266 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
267 item = [menu itemWithTitle:title];
269 EXPECT_EQ(@selector(openAllBookmarks:), [item action]);
270 EXPECT_TRUE([item isEnabled]);
272 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
273 item = [menu itemWithTitle:title];
275 EXPECT_EQ(@selector(openAllBookmarksNewWindow:), [item action]);
276 EXPECT_TRUE([item isEnabled]);
278 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
279 item = [menu itemWithTitle:title];
281 EXPECT_EQ(@selector(openAllBookmarksIncognitoWindow:), [item action]);
282 EXPECT_TRUE([item isEnabled]);
284 ClearBookmarkMenu(bridge_.get(), menu);
285 EXPECT_EQ(0, [menu numberOfItems]);
287 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
288 IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, false);
289 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
290 IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, false);
291 AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
292 IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, false);
293 EXPECT_EQ(3, [menu numberOfItems]);
295 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
296 item = [menu itemWithTitle:title];
298 EXPECT_EQ(nil, [item action]);
299 EXPECT_FALSE([item isEnabled]);
301 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
302 item = [menu itemWithTitle:title];
304 EXPECT_EQ(nil, [item action]);
305 EXPECT_FALSE([item isEnabled]);
307 title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
308 item = [menu itemWithTitle:title];
310 EXPECT_EQ(nil, [item action]);
311 EXPECT_FALSE([item isEnabled]);
314 // Makes sure our internal map of BookmarkNode to NSMenuItem works.
315 TEST_F(BookmarkMenuBridgeTest, TestGetMenuItemForNode) {
316 base::string16 empty;
317 NSMenu* menu = bridge_->menu_;
319 BookmarkModel* model = bridge_->GetBookmarkModel();
320 const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
321 const BookmarkNode* root = model->AddFolder(bookmark_bar, 0, empty);
322 EXPECT_TRUE(model && root);
324 model->AddURL(root, 0, ASCIIToUTF16("Test Item"), GURL("http://test"));
325 AddNodeToMenu(bridge_.get(), root, menu);
326 EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
328 model->AddURL(root, 1, ASCIIToUTF16("Test 2"), GURL("http://second-test"));
329 AddNodeToMenu(bridge_.get(), root, menu);
330 EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
331 EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(1)));
333 const BookmarkNode* removed_node = root->GetChild(0);
334 EXPECT_EQ(2, root->child_count());
335 model->Remove(root, 0);
336 EXPECT_EQ(1, root->child_count());
337 bridge_->UpdateMenu(menu);
338 EXPECT_FALSE(MenuItemForNode(bridge_.get(), removed_node));
339 EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
341 const BookmarkNode empty_node(GURL("http://no-where/"));
342 EXPECT_FALSE(MenuItemForNode(bridge_.get(), &empty_node));
343 EXPECT_FALSE(MenuItemForNode(bridge_.get(), NULL));
346 // Test that Loaded() adds both the bookmark bar nodes and the "other" nodes.
347 TEST_F(BookmarkMenuBridgeTest, TestAddNodeToOther) {
348 NSMenu* menu = bridge_->menu_;
350 BookmarkModel* model = bridge_->GetBookmarkModel();
351 const BookmarkNode* root = model->other_node();
352 EXPECT_TRUE(model && root);
354 const char* short_url = "http://foo/";
355 model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
357 bridge_->UpdateMenu(menu);
358 ASSERT_GT([menu numberOfItems], 0);
359 NSMenuItem* other = [menu itemAtIndex:([menu numberOfItems]-1)];
361 EXPECT_TRUE([other hasSubmenu]);
362 ASSERT_GT([[other submenu] numberOfItems], 0);
363 EXPECT_NSEQ(@"http://foo/", [[[other submenu] itemAtIndex:0] title]);
366 TEST_F(BookmarkMenuBridgeTest, TestFaviconLoading) {
367 NSMenu* menu = bridge_->menu_;
369 BookmarkModel* model = bridge_->GetBookmarkModel();
370 const BookmarkNode* root = model->bookmark_bar_node();
371 EXPECT_TRUE(model && root);
373 const BookmarkNode* node =
374 model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
375 GURL("http://favicon-test"));
376 bridge_->UpdateMenu(menu);
377 NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
378 EXPECT_TRUE([item image]);
380 bridge_->BookmarkNodeFaviconChanged(model, node);
381 EXPECT_TRUE([item image]);
384 TEST_F(BookmarkMenuBridgeTest, TestChangeTitle) {
385 NSMenu* menu = bridge_->menu_;
386 BookmarkModel* model = bridge_->GetBookmarkModel();
387 const BookmarkNode* root = model->bookmark_bar_node();
388 EXPECT_TRUE(model && root);
390 const BookmarkNode* node =
391 model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
392 GURL("http://title-test"));
393 bridge_->UpdateMenu(menu);
394 NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
395 EXPECT_TRUE([item image]);
397 model->SetTitle(node, ASCIIToUTF16("New Title"));
399 item = [menu itemWithTitle:@"Test Item"];
401 item = [menu itemWithTitle:@"New Title"];