spelling_menu_observer_browsertest.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2014 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/renderer_context_menu/spelling_menu_observer.h" 6 7#include <vector> 8 9#include "base/prefs/pref_service.h" 10#include "base/strings/utf_string_conversions.h" 11#include "chrome/app/chrome_command_ids.h" 12#include "chrome/browser/renderer_context_menu/render_view_context_menu.h" 13#include "chrome/browser/spellchecker/spelling_service_client.h" 14#include "chrome/common/pref_names.h" 15#include "chrome/test/base/in_process_browser_test.h" 16#include "chrome/test/base/testing_profile.h" 17#include "components/renderer_context_menu/render_view_context_menu_observer.h" 18 19using content::RenderViewHost; 20using content::WebContents; 21 22namespace { 23 24// A mock context menu used in this test. This class overrides virtual methods 25// derived from the RenderViewContextMenuProxy class to monitor calls from the 26// SpellingMenuObserver class. 27class MockRenderViewContextMenu : public RenderViewContextMenuProxy { 28 public: 29 // A menu item used in this test. This test uses a vector of this struct to 30 // hold menu items added by this test. 31 struct MockMenuItem { 32 MockMenuItem() 33 : command_id(0), 34 enabled(false), 35 checked(false), 36 hidden(true) { 37 } 38 int command_id; 39 bool enabled; 40 bool checked; 41 bool hidden; 42 base::string16 title; 43 }; 44 45 explicit MockRenderViewContextMenu(bool incognito); 46 virtual ~MockRenderViewContextMenu(); 47 48 // RenderViewContextMenuProxy implementation. 49 virtual void AddMenuItem(int command_id, 50 const base::string16& title) OVERRIDE; 51 virtual void AddCheckItem(int command_id, 52 const base::string16& title) OVERRIDE; 53 virtual void AddSeparator() OVERRIDE; 54 virtual void AddSubMenu(int command_id, 55 const base::string16& label, 56 ui::MenuModel* model) OVERRIDE; 57 virtual void UpdateMenuItem(int command_id, 58 bool enabled, 59 bool hidden, 60 const base::string16& title) OVERRIDE; 61 virtual RenderViewHost* GetRenderViewHost() const OVERRIDE; 62 virtual WebContents* GetWebContents() const OVERRIDE; 63 virtual content::BrowserContext* GetBrowserContext() const OVERRIDE; 64 65 // Attaches a RenderViewContextMenuObserver to be tested. 66 void SetObserver(RenderViewContextMenuObserver* observer); 67 68 // Returns the number of items added by the test. 69 size_t GetMenuSize() const; 70 71 // Returns the i-th item. 72 bool GetMenuItem(size_t i, MockMenuItem* item) const; 73 74 // Returns the writable profile used in this test. 75 PrefService* GetPrefs(); 76 77 private: 78 // An observer used for initializing the status of menu items added in this 79 // test. A test should delete this RenderViewContextMenuObserver object. 80 RenderViewContextMenuObserver* observer_; 81 82 // A dummy profile used in this test. Call GetPrefs() when a test needs to 83 // change this profile and use PrefService methods. 84 scoped_ptr<TestingProfile> original_profile_; 85 86 // Either |original_profile_| or its incognito profile. 87 Profile* profile_; 88 89 // A list of menu items added by the SpellingMenuObserver class. 90 std::vector<MockMenuItem> items_; 91 92 DISALLOW_COPY_AND_ASSIGN(MockRenderViewContextMenu); 93}; 94 95MockRenderViewContextMenu::MockRenderViewContextMenu(bool incognito) 96 : observer_(NULL) { 97 original_profile_ = TestingProfile::Builder().Build(); 98 profile_ = incognito ? original_profile_->GetOffTheRecordProfile() 99 : original_profile_.get(); 100} 101 102MockRenderViewContextMenu::~MockRenderViewContextMenu() { 103} 104 105void MockRenderViewContextMenu::AddMenuItem(int command_id, 106 const base::string16& title) { 107 MockMenuItem item; 108 item.command_id = command_id; 109 item.enabled = observer_->IsCommandIdEnabled(command_id); 110 item.checked = false; 111 item.hidden = false; 112 item.title = title; 113 items_.push_back(item); 114} 115 116void MockRenderViewContextMenu::AddCheckItem(int command_id, 117 const base::string16& title) { 118 MockMenuItem item; 119 item.command_id = command_id; 120 item.enabled = observer_->IsCommandIdEnabled(command_id); 121 item.checked = observer_->IsCommandIdChecked(command_id); 122 item.hidden = false; 123 item.title = title; 124 items_.push_back(item); 125} 126 127void MockRenderViewContextMenu::AddSeparator() { 128 MockMenuItem item; 129 item.command_id = -1; 130 item.enabled = false; 131 item.checked = false; 132 item.hidden = false; 133 items_.push_back(item); 134} 135 136void MockRenderViewContextMenu::AddSubMenu(int command_id, 137 const base::string16& label, 138 ui::MenuModel* model) { 139 MockMenuItem item; 140 item.command_id = -1; 141 item.enabled = false; 142 item.checked = false; 143 item.hidden = false; 144 items_.push_back(item); 145} 146 147void MockRenderViewContextMenu::UpdateMenuItem(int command_id, 148 bool enabled, 149 bool hidden, 150 const base::string16& title) { 151 for (std::vector<MockMenuItem>::iterator it = items_.begin(); 152 it != items_.end(); ++it) { 153 if (it->command_id == command_id) { 154 it->enabled = enabled; 155 it->hidden = hidden; 156 it->title = title; 157 return; 158 } 159 } 160 161 // The SpellingMenuObserver class tries to change a menu item not added by the 162 // class. This is an unexpected behavior and we should stop now. 163 FAIL(); 164} 165 166RenderViewHost* MockRenderViewContextMenu::GetRenderViewHost() const { 167 return NULL; 168} 169 170WebContents* MockRenderViewContextMenu::GetWebContents() const { 171 return NULL; 172} 173 174content::BrowserContext* MockRenderViewContextMenu::GetBrowserContext() const { 175 return profile_; 176} 177 178size_t MockRenderViewContextMenu::GetMenuSize() const { 179 return items_.size(); 180} 181 182bool MockRenderViewContextMenu::GetMenuItem(size_t i, 183 MockMenuItem* item) const { 184 if (i >= items_.size()) 185 return false; 186 item->command_id = items_[i].command_id; 187 item->enabled = items_[i].enabled; 188 item->checked = items_[i].checked; 189 item->hidden = items_[i].hidden; 190 item->title = items_[i].title; 191 return true; 192} 193 194void MockRenderViewContextMenu::SetObserver( 195 RenderViewContextMenuObserver* observer) { 196 observer_ = observer; 197} 198 199PrefService* MockRenderViewContextMenu::GetPrefs() { 200 return profile_->GetPrefs(); 201} 202 203// A test class used in this file. This test should be a browser test because it 204// accesses resources. 205class SpellingMenuObserverTest : public InProcessBrowserTest { 206 public: 207 SpellingMenuObserverTest(); 208 209 virtual void SetUpOnMainThread() OVERRIDE { 210 Reset(false); 211 } 212 213 virtual void TearDownOnMainThread() OVERRIDE { 214 observer_.reset(); 215 menu_.reset(); 216 } 217 218 void Reset(bool incognito) { 219 observer_.reset(); 220 menu_.reset(new MockRenderViewContextMenu(incognito)); 221 observer_.reset(new SpellingMenuObserver(menu_.get())); 222 menu_->SetObserver(observer_.get()); 223 } 224 225 void InitMenu(const char* word, const char* suggestion) { 226 content::ContextMenuParams params; 227 params.is_editable = true; 228 params.misspelled_word = base::ASCIIToUTF16(word); 229 params.dictionary_suggestions.clear(); 230 if (suggestion) 231 params.dictionary_suggestions.push_back(base::ASCIIToUTF16(suggestion)); 232 observer_->InitMenu(params); 233 } 234 235 void ForceSuggestMode() { 236 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 237 // Force a non-empty and non-"en" locale so SUGGEST is available. 238 menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "fr"); 239 ASSERT_TRUE(SpellingServiceClient::IsAvailable( 240 menu()->GetBrowserContext(), SpellingServiceClient::SUGGEST)); 241 ASSERT_FALSE(SpellingServiceClient::IsAvailable( 242 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK)); 243 } 244 245 virtual ~SpellingMenuObserverTest(); 246 MockRenderViewContextMenu* menu() { return menu_.get(); } 247 SpellingMenuObserver* observer() { return observer_.get(); } 248 private: 249 scoped_ptr<SpellingMenuObserver> observer_; 250 scoped_ptr<MockRenderViewContextMenu> menu_; 251 DISALLOW_COPY_AND_ASSIGN(SpellingMenuObserverTest); 252}; 253 254SpellingMenuObserverTest::SpellingMenuObserverTest() { 255} 256 257SpellingMenuObserverTest::~SpellingMenuObserverTest() { 258} 259 260} // namespace 261 262// Tests that right-clicking a correct word does not add any items. 263IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, InitMenuWithCorrectWord) { 264 InitMenu("", NULL); 265 EXPECT_EQ(static_cast<size_t>(0), menu()->GetMenuSize()); 266} 267 268// Tests that right-clicking a misspelled word adds four items: 269// "No spelling suggestions", "Add to dictionary", "Ask Google for suggestions", 270// and a separator. 271IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, InitMenuWithMisspelledWord) { 272 InitMenu("wiimode", NULL); 273 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize()); 274 275 // Read all the context-menu items added by this test and verify they are 276 // expected ones. We do not check the item titles to prevent resource changes 277 // from breaking this test. (I think it is not expected by those who change 278 // resources.) 279 MockRenderViewContextMenu::MockMenuItem item; 280 menu()->GetMenuItem(0, &item); 281 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS, item.command_id); 282 EXPECT_FALSE(item.enabled); 283 EXPECT_FALSE(item.hidden); 284 menu()->GetMenuItem(1, &item); 285 EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY, item.command_id); 286 EXPECT_TRUE(item.enabled); 287 EXPECT_FALSE(item.hidden); 288 menu()->GetMenuItem(2, &item); 289 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE, item.command_id); 290 EXPECT_TRUE(item.enabled); 291 EXPECT_FALSE(item.checked); 292 EXPECT_FALSE(item.hidden); 293 menu()->GetMenuItem(3, &item); 294 EXPECT_EQ(-1, item.command_id); 295 EXPECT_FALSE(item.enabled); 296 EXPECT_FALSE(item.hidden); 297} 298 299// Tests that right-clicking a correct word when we enable spelling-service 300// integration to verify an item "Ask Google for suggestions" is checked. Even 301// though this meanu itself does not add this item, its sub-menu adds the item 302// and calls SpellingMenuObserver::IsChecked() to check it. 303IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, 304 EnableSpellingServiceWithCorrectWord) { 305 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 306 InitMenu("", NULL); 307 308 EXPECT_TRUE( 309 observer()->IsCommandIdChecked(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE)); 310} 311 312// Tests that right-clicking a misspelled word when we enable spelling-service 313// integration to verify an item "Ask Google for suggestions" is checked. (This 314// test does not actually send JSON-RPC requests to the service because it makes 315// this test flaky.) 316IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, EnableSpellingService) { 317 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 318 menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, std::string()); 319 320 InitMenu("wiimode", NULL); 321 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize()); 322 323 // To avoid duplicates, this test reads only the "Ask Google for suggestions" 324 // item and verifies it is enabled and checked. 325 MockRenderViewContextMenu::MockMenuItem item; 326 menu()->GetMenuItem(2, &item); 327 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE, item.command_id); 328 EXPECT_TRUE(item.enabled); 329 EXPECT_TRUE(item.checked); 330 EXPECT_FALSE(item.hidden); 331} 332 333// Test that there will be a separator after "no suggestions" if 334// SpellingServiceClient::SUGGEST is on. 335IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, SeparatorAfterSuggestions) { 336 ForceSuggestMode(); 337 InitMenu("jhhj", NULL); 338 339 // The test should see a top separator, "No spelling suggestions", 340 // "No more Google suggestions" (from SpellingService) and a separator 341 // as the first four items, then possibly more (not relevant here). 342 EXPECT_LT(4U, menu()->GetMenuSize()); 343 344 MockRenderViewContextMenu::MockMenuItem item; 345 menu()->GetMenuItem(0, &item); 346 EXPECT_EQ(-1, item.command_id); 347 EXPECT_FALSE(item.enabled); 348 EXPECT_FALSE(item.hidden); 349 350 menu()->GetMenuItem(1, &item); 351 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, item.command_id); 352 EXPECT_FALSE(item.enabled); 353 EXPECT_FALSE(item.hidden); 354 355 menu()->GetMenuItem(2, &item); 356 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS, item.command_id); 357 EXPECT_FALSE(item.enabled); 358 EXPECT_FALSE(item.hidden); 359 360 menu()->GetMenuItem(3, &item); 361 EXPECT_EQ(-1, item.command_id); 362 EXPECT_FALSE(item.enabled); 363 EXPECT_FALSE(item.hidden); 364} 365 366// Test that we don't show "No more suggestions from Google" if the spelling 367// service is enabled and that there is only one suggestion. 368IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, 369 NoMoreSuggestionsNotDisplayed) { 370 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 371 372 // Force a non-empty locale so SPELLCHECK is available. 373 menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "en"); 374 EXPECT_TRUE(SpellingServiceClient::IsAvailable( 375 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK)); 376 InitMenu("asdfkj", "asdf"); 377 378 // The test should see a separator, a suggestion and another separator 379 // as the first two items, then possibly more (not relevant here). 380 EXPECT_LT(3U, menu()->GetMenuSize()); 381 382 MockRenderViewContextMenu::MockMenuItem item; 383 menu()->GetMenuItem(0, &item); 384 EXPECT_EQ(-1, item.command_id); 385 EXPECT_FALSE(item.enabled); 386 EXPECT_FALSE(item.hidden); 387 388 menu()->GetMenuItem(1, &item); 389 EXPECT_EQ(IDC_SPELLCHECK_SUGGESTION_0, item.command_id); 390 EXPECT_TRUE(item.enabled); 391 EXPECT_FALSE(item.hidden); 392 393 menu()->GetMenuItem(2, &item); 394 EXPECT_EQ(-1, item.command_id); 395 EXPECT_FALSE(item.enabled); 396 EXPECT_FALSE(item.hidden); 397} 398 399// Test that "Ask Google For Suggestions" is grayed out when using an 400// off the record profile. 401// TODO(rlp): Include graying out of autocorrect in this test when autocorrect 402// is functional. 403IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, 404 NoSpellingServiceWhenOffTheRecord) { 405 // Create a menu in an incognito profile. 406 Reset(true); 407 408 // This means spellchecking is allowed. Default is that the service is 409 // contacted but this test makes sure that if profile is incognito, that 410 // is not an option. 411 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 412 413 // Force a non-empty locale so SUGGEST normally would be available. 414 menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "en"); 415 EXPECT_FALSE(SpellingServiceClient::IsAvailable( 416 menu()->GetBrowserContext(), SpellingServiceClient::SUGGEST)); 417 EXPECT_FALSE(SpellingServiceClient::IsAvailable( 418 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK)); 419 420 InitMenu("sjxdjiiiiii", NULL); 421 422 // The test should see "No spelling suggestions" (from system checker). 423 // They should not see "No more Google suggestions" (from SpellingService) or 424 // a separator. The next 2 items should be "Add to Dictionary" followed 425 // by "Ask Google for suggestions" which should be disabled. 426 // TODO(rlp): add autocorrect here when it is functional. 427 EXPECT_LT(3U, menu()->GetMenuSize()); 428 429 MockRenderViewContextMenu::MockMenuItem item; 430 menu()->GetMenuItem(0, &item); 431 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS, item.command_id); 432 EXPECT_FALSE(item.enabled); 433 EXPECT_FALSE(item.hidden); 434 435 menu()->GetMenuItem(1, &item); 436 EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY, item.command_id); 437 EXPECT_TRUE(item.enabled); 438 EXPECT_FALSE(item.hidden); 439 440 menu()->GetMenuItem(2, &item); 441 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE, item.command_id); 442 EXPECT_FALSE(item.enabled); 443 EXPECT_FALSE(item.hidden); 444} 445 446// Test that the menu is preceeded by a separator if there are any suggestions, 447// or if the SpellingServiceClient is available 448IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, SuggestionsForceTopSeparator) { 449 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, false); 450 451 // First case: Misspelled word, no suggestions, no spellcheck service. 452 InitMenu("asdfkj", NULL); 453 // See SpellingMenuObserverTest.InitMenuWithMisspelledWord on why 4 items. 454 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize()); 455 MockRenderViewContextMenu::MockMenuItem item; 456 menu()->GetMenuItem(0, &item); 457 EXPECT_NE(-1, item.command_id); 458 459 // Case #2. Misspelled word, suggestions, no spellcheck service. 460 Reset(false); 461 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, false); 462 InitMenu("asdfkj", "asdf"); 463 464 // Expect at least separator and 4 default entries. 465 EXPECT_LT(static_cast<size_t>(5), menu()->GetMenuSize()); 466 // This test only cares that the first one is a separator. 467 menu()->GetMenuItem(0, &item); 468 EXPECT_EQ(-1, item.command_id); 469 470 // Case #3. Misspelled word, suggestion service is on. 471 Reset(false); 472 ForceSuggestMode(); 473 InitMenu("asdfkj", NULL); 474 475 // Should have at least 2 entries. Separator, suggestion. 476 EXPECT_LT(2U, menu()->GetMenuSize()); 477 menu()->GetMenuItem(0, &item); 478 EXPECT_EQ(-1, item.command_id); 479 menu()->GetMenuItem(1, &item); 480 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, item.command_id); 481} 482