// Copyright (c) 2012 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. #include <stddef.h> #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h" #include "chrome/browser/ui/autofill/autofill_popup_view.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" #include "components/autofill/content/browser/content_autofill_driver.h" #include "components/autofill/content/browser/content_autofill_driver_factory.h" #include "components/autofill/core/browser/autofill_external_delegate.h" #include "components/autofill/core/browser/autofill_manager.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/popup_item_ids.h" #include "components/autofill/core/browser/test_autofill_client.h" #include "components/autofill/core/browser/test_autofill_external_delegate.h" #include "components/prefs/pref_service.h" #include "content/public/browser/web_contents.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/display.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/text_utils.h" using ::testing::_; using ::testing::AtLeast; using ::testing::NiceMock; using base::ASCIIToUTF16; using base::WeakPtr; namespace autofill { namespace { class MockAutofillExternalDelegate : public AutofillExternalDelegate { public: MockAutofillExternalDelegate(AutofillManager* autofill_manager, AutofillDriver* autofill_driver) : AutofillExternalDelegate(autofill_manager, autofill_driver) {} ~MockAutofillExternalDelegate() override {} void DidSelectSuggestion(const base::string16& value, int identifier) override {} bool RemoveSuggestion(const base::string16& value, int identifier) override { return true; } void ClearPreviewedForm() override {} base::WeakPtr<AutofillExternalDelegate> GetWeakPtr() { return AutofillExternalDelegate::GetWeakPtr(); } }; class MockAutofillClient : public autofill::TestAutofillClient { public: MockAutofillClient() : prefs_(autofill::test::PrefServiceForTesting()) {} ~MockAutofillClient() override {} PrefService* GetPrefs() override { return prefs_.get(); } private: scoped_ptr<PrefService> prefs_; DISALLOW_COPY_AND_ASSIGN(MockAutofillClient); }; class TestAutofillPopupController : public AutofillPopupControllerImpl { public: TestAutofillPopupController( base::WeakPtr<AutofillExternalDelegate> external_delegate, const gfx::RectF& element_bounds) : AutofillPopupControllerImpl(external_delegate, NULL, NULL, element_bounds, base::i18n::UNKNOWN_DIRECTION) {} ~TestAutofillPopupController() override {} // Making protected functions public for testing using AutofillPopupControllerImpl::GetLineCount; using AutofillPopupControllerImpl::GetSuggestionAt; using AutofillPopupControllerImpl::GetElidedValueAt; using AutofillPopupControllerImpl::GetElidedLabelAt; using AutofillPopupControllerImpl::selected_line; using AutofillPopupControllerImpl::SetSelectedLine; using AutofillPopupControllerImpl::SelectNextLine; using AutofillPopupControllerImpl::SelectPreviousLine; using AutofillPopupControllerImpl::RemoveSelectedLine; using AutofillPopupControllerImpl::popup_bounds; using AutofillPopupControllerImpl::element_bounds; #if !defined(OS_ANDROID) using AutofillPopupControllerImpl::GetValueFontListForRow; using AutofillPopupControllerImpl::GetLabelFontList; #endif using AutofillPopupControllerImpl::SetValues; using AutofillPopupControllerImpl::GetWeakPtr; MOCK_METHOD1(InvalidateRow, void(size_t)); MOCK_METHOD0(UpdateBoundsAndRedrawPopup, void()); MOCK_METHOD0(Hide, void()); void DoHide() { AutofillPopupControllerImpl::Hide(); } private: void ShowView() override {} }; } // namespace class AutofillPopupControllerUnitTest : public ChromeRenderViewHostTestHarness { public: AutofillPopupControllerUnitTest() : autofill_client_(new MockAutofillClient()), autofill_popup_controller_(NULL) {} ~AutofillPopupControllerUnitTest() override {} void SetUp() override { ChromeRenderViewHostTestHarness::SetUp(); ContentAutofillDriverFactory::CreateForWebContentsAndDelegate( web_contents(), autofill_client_.get(), "en-US", AutofillManager::ENABLE_AUTOFILL_DOWNLOAD_MANAGER); // Make sure RenderFrame is created. NavigateAndCommit(GURL("about:blank")); ContentAutofillDriverFactory* factory = ContentAutofillDriverFactory::FromWebContents(web_contents()); ContentAutofillDriver* driver = factory->DriverForFrame(web_contents()->GetMainFrame()); external_delegate_.reset( new NiceMock<MockAutofillExternalDelegate>( driver->autofill_manager(), driver)); autofill_popup_controller_ = new testing::NiceMock<TestAutofillPopupController>( external_delegate_->GetWeakPtr(), gfx::RectF()); } void TearDown() override { // This will make sure the controller and the view (if any) are both // cleaned up. if (autofill_popup_controller_) autofill_popup_controller_->DoHide(); external_delegate_.reset(); ChromeRenderViewHostTestHarness::TearDown(); } TestAutofillPopupController* popup_controller() { return autofill_popup_controller_; } MockAutofillExternalDelegate* delegate() { return external_delegate_.get(); } protected: scoped_ptr<MockAutofillClient> autofill_client_; scoped_ptr<NiceMock<MockAutofillExternalDelegate> > external_delegate_; testing::NiceMock<TestAutofillPopupController>* autofill_popup_controller_; }; TEST_F(AutofillPopupControllerUnitTest, ChangeSelectedLine) { // Set up the popup. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 0)); suggestions.push_back(Suggestion("", "", "", 0)); autofill_popup_controller_->Show(suggestions); EXPECT_LT(autofill_popup_controller_->selected_line(), 0); // Check that there are at least 2 values so that the first and last selection // are different. EXPECT_GE(2, static_cast<int>(autofill_popup_controller_->GetLineCount())); // Test wrapping before the front. autofill_popup_controller_->SelectPreviousLine(); EXPECT_EQ(static_cast<int>( autofill_popup_controller_->GetLineCount() - 1), autofill_popup_controller_->selected_line()); // Test wrapping after the end. autofill_popup_controller_->SelectNextLine(); EXPECT_EQ(0, autofill_popup_controller_->selected_line()); } TEST_F(AutofillPopupControllerUnitTest, RedrawSelectedLine) { // Set up the popup. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 0)); suggestions.push_back(Suggestion("", "", "", 0)); autofill_popup_controller_->Show(suggestions); // Make sure that when a new line is selected, it is invalidated so it can // be updated to show it is selected. int selected_line = 0; EXPECT_CALL(*autofill_popup_controller_, InvalidateRow(selected_line)); autofill_popup_controller_->SetSelectedLine(selected_line); // Ensure that the row isn't invalidated if it didn't change. EXPECT_CALL(*autofill_popup_controller_, InvalidateRow(selected_line)).Times(0); autofill_popup_controller_->SetSelectedLine(selected_line); // Change back to no selection. EXPECT_CALL(*autofill_popup_controller_, InvalidateRow(selected_line)); autofill_popup_controller_->SetSelectedLine(-1); } TEST_F(AutofillPopupControllerUnitTest, RemoveLine) { // Set up the popup. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 1)); suggestions.push_back(Suggestion("", "", "", 1)); suggestions.push_back(Suggestion("", "", "", POPUP_ITEM_ID_AUTOFILL_OPTIONS)); autofill_popup_controller_->Show(suggestions); // Generate a popup, so it can be hidden later. It doesn't matter what the // external_delegate thinks is being shown in the process, since we are just // testing the popup here. autofill::GenerateTestAutofillPopup(external_delegate_.get()); // No line is selected so the removal should fail. EXPECT_FALSE(autofill_popup_controller_->RemoveSelectedLine()); // Remove the first entry. The popup should be redrawn since its size has // changed. EXPECT_CALL(*autofill_popup_controller_, UpdateBoundsAndRedrawPopup()); autofill_popup_controller_->SetSelectedLine(0); EXPECT_TRUE(autofill_popup_controller_->RemoveSelectedLine()); // Remove the last entry. The popup should then be hidden since there are // no Autofill entries left. EXPECT_CALL(*autofill_popup_controller_, Hide()); autofill_popup_controller_->SetSelectedLine(0); EXPECT_TRUE(autofill_popup_controller_->RemoveSelectedLine()); } TEST_F(AutofillPopupControllerUnitTest, RemoveOnlyLine) { // Set up the popup. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 1)); autofill_popup_controller_->Show(suggestions); // Generate a popup. autofill::GenerateTestAutofillPopup(external_delegate_.get()); // Select the only line. autofill_popup_controller_->SetSelectedLine(0); // Remove the only line. There should be no row invalidation and the popup // should then be hidden since there are no Autofill entries left. EXPECT_CALL(*autofill_popup_controller_, Hide()); EXPECT_CALL(*autofill_popup_controller_, InvalidateRow(_)).Times(0); EXPECT_TRUE(autofill_popup_controller_->RemoveSelectedLine()); } TEST_F(AutofillPopupControllerUnitTest, SkipSeparator) { // Set up the popup. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 1)); suggestions.push_back(Suggestion("", "", "", POPUP_ITEM_ID_SEPARATOR)); suggestions.push_back(Suggestion("", "", "", POPUP_ITEM_ID_AUTOFILL_OPTIONS)); autofill_popup_controller_->Show(suggestions); autofill_popup_controller_->SetSelectedLine(0); // Make sure next skips the unselectable separator. autofill_popup_controller_->SelectNextLine(); EXPECT_EQ(2, autofill_popup_controller_->selected_line()); // Make sure previous skips the unselectable separator. autofill_popup_controller_->SelectPreviousLine(); EXPECT_EQ(0, autofill_popup_controller_->selected_line()); } TEST_F(AutofillPopupControllerUnitTest, UpdateDataListValues) { std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 1)); autofill_popup_controller_->Show(suggestions); // Add one data list entry. base::string16 value1 = ASCIIToUTF16("data list value 1"); std::vector<base::string16> data_list_values; data_list_values.push_back(value1); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); ASSERT_EQ(3u, autofill_popup_controller_->GetLineCount()); Suggestion result0 = autofill_popup_controller_->GetSuggestionAt(0); EXPECT_EQ(value1, result0.value); EXPECT_EQ(value1, autofill_popup_controller_->GetElidedValueAt(0)); EXPECT_EQ(value1, result0.label); EXPECT_EQ(value1, autofill_popup_controller_->GetElidedLabelAt(0)); EXPECT_EQ(POPUP_ITEM_ID_DATALIST_ENTRY, result0.frontend_id); Suggestion result1 = autofill_popup_controller_->GetSuggestionAt(1); EXPECT_EQ(base::string16(), result1.value); EXPECT_EQ(base::string16(), result1.label); EXPECT_EQ(POPUP_ITEM_ID_SEPARATOR, result1.frontend_id); Suggestion result2 = autofill_popup_controller_->GetSuggestionAt(2); EXPECT_EQ(base::string16(), result2.value); EXPECT_EQ(base::string16(), result2.label); EXPECT_EQ(1, result2.frontend_id); // Add two data list entries (which should replace the current one). base::string16 value2 = ASCIIToUTF16("data list value 2"); data_list_values.push_back(value2); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); ASSERT_EQ(4u, autofill_popup_controller_->GetLineCount()); // Original one first, followed by new one, then separator. EXPECT_EQ(value1, autofill_popup_controller_->GetSuggestionAt(0).value); EXPECT_EQ(value1, autofill_popup_controller_->GetElidedValueAt(0)); EXPECT_EQ(value2, autofill_popup_controller_->GetSuggestionAt(1).value); EXPECT_EQ(value2, autofill_popup_controller_->GetElidedValueAt(1)); EXPECT_EQ(POPUP_ITEM_ID_SEPARATOR, autofill_popup_controller_->GetSuggestionAt(2).frontend_id); // Clear all data list values. data_list_values.clear(); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); ASSERT_EQ(1u, autofill_popup_controller_->GetLineCount()); EXPECT_EQ(1, autofill_popup_controller_->GetSuggestionAt(0).frontend_id); } TEST_F(AutofillPopupControllerUnitTest, PopupsWithOnlyDataLists) { // Create the popup with a single datalist element. std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", POPUP_ITEM_ID_DATALIST_ENTRY)); autofill_popup_controller_->Show(suggestions); // Replace the datalist element with a new one. base::string16 value1 = ASCIIToUTF16("data list value 1"); std::vector<base::string16> data_list_values; data_list_values.push_back(value1); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); ASSERT_EQ(1u, autofill_popup_controller_->GetLineCount()); EXPECT_EQ(value1, autofill_popup_controller_->GetSuggestionAt(0).value); EXPECT_EQ(POPUP_ITEM_ID_DATALIST_ENTRY, autofill_popup_controller_->GetSuggestionAt(0).frontend_id); // Clear datalist values and check that the popup becomes hidden. EXPECT_CALL(*autofill_popup_controller_, Hide()); data_list_values.clear(); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); } TEST_F(AutofillPopupControllerUnitTest, GetOrCreate) { ContentAutofillDriverFactory* factory = ContentAutofillDriverFactory::FromWebContents(web_contents()); ContentAutofillDriver* driver = factory->DriverForFrame(web_contents()->GetMainFrame()); MockAutofillExternalDelegate delegate(driver->autofill_manager(), driver); WeakPtr<AutofillPopupControllerImpl> controller = AutofillPopupControllerImpl::GetOrCreate( WeakPtr<AutofillPopupControllerImpl>(), delegate.GetWeakPtr(), NULL, NULL, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION); EXPECT_TRUE(controller.get()); controller->Hide(); controller = AutofillPopupControllerImpl::GetOrCreate( WeakPtr<AutofillPopupControllerImpl>(), delegate.GetWeakPtr(), NULL, NULL, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION); EXPECT_TRUE(controller.get()); WeakPtr<AutofillPopupControllerImpl> controller2 = AutofillPopupControllerImpl::GetOrCreate( controller, delegate.GetWeakPtr(), NULL, NULL, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION); EXPECT_EQ(controller.get(), controller2.get()); controller->Hide(); testing::NiceMock<TestAutofillPopupController>* test_controller = new testing::NiceMock<TestAutofillPopupController>(delegate.GetWeakPtr(), gfx::RectF()); EXPECT_CALL(*test_controller, Hide()); gfx::RectF bounds(0.f, 0.f, 1.f, 2.f); base::WeakPtr<AutofillPopupControllerImpl> controller3 = AutofillPopupControllerImpl::GetOrCreate( test_controller->GetWeakPtr(), delegate.GetWeakPtr(), NULL, NULL, bounds, base::i18n::UNKNOWN_DIRECTION); EXPECT_EQ( bounds, static_cast<AutofillPopupController*>(controller3.get())-> element_bounds()); controller3->Hide(); // Hide the test_controller to delete it. test_controller->DoHide(); } TEST_F(AutofillPopupControllerUnitTest, ProperlyResetController) { std::vector<Suggestion> suggestions; suggestions.push_back(Suggestion("", "", "", 0)); suggestions.push_back(Suggestion("", "", "", 0)); popup_controller()->SetValues(suggestions); popup_controller()->SetSelectedLine(0); // Now show a new popup with the same controller, but with fewer items. WeakPtr<AutofillPopupControllerImpl> controller = AutofillPopupControllerImpl::GetOrCreate( popup_controller()->GetWeakPtr(), delegate()->GetWeakPtr(), NULL, NULL, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION); EXPECT_NE(0, controller->selected_line()); EXPECT_EQ(0u, controller->GetLineCount()); } #if !defined(OS_ANDROID) TEST_F(AutofillPopupControllerUnitTest, ElideText) { std::vector<Suggestion> suggestions; suggestions.push_back( Suggestion("Text that will need to be trimmed", "Label that will be trimmed", "genericCC", 0)); suggestions.push_back( Suggestion("untrimmed", "Untrimmed", "genericCC", 0)); autofill_popup_controller_->SetValues(suggestions); // Ensure the popup will be too small to display all of the first row. int popup_max_width = gfx::GetStringWidth( suggestions[0].value, autofill_popup_controller_->GetValueFontListForRow(0)) + gfx::GetStringWidth( suggestions[0].label, autofill_popup_controller_->GetLabelFontList()) - 25; autofill_popup_controller_->ElideValueAndLabelForRow(0, popup_max_width); // The first element was long so it should have been trimmed. EXPECT_NE(autofill_popup_controller_->GetSuggestionAt(0).value, autofill_popup_controller_->GetElidedValueAt(0)); EXPECT_NE(autofill_popup_controller_->GetSuggestionAt(0).label, autofill_popup_controller_->GetElidedLabelAt(0)); autofill_popup_controller_->ElideValueAndLabelForRow(1, popup_max_width); // The second element was shorter so it should be unchanged. EXPECT_EQ(autofill_popup_controller_->GetSuggestionAt(1).value, autofill_popup_controller_->GetElidedValueAt(1)); EXPECT_EQ(autofill_popup_controller_->GetSuggestionAt(1).label, autofill_popup_controller_->GetElidedLabelAt(1)); } #endif } // namespace autofill