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