// 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 "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.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/autofill_driver_impl.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/test_autofill_external_delegate.h" #include "components/autofill/core/browser/test_autofill_manager_delegate.h" #include "grit/webkit_resources.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/web/WebAutofillClient.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/display.h" #include "ui/gfx/rect.h" using ::testing::_; using ::testing::AtLeast; using ::testing::NiceMock; using base::WeakPtr; using blink::WebAutofillClient; namespace autofill { namespace { class MockAutofillExternalDelegate : public AutofillExternalDelegate { public: MockAutofillExternalDelegate(AutofillManager* autofill_manager, AutofillDriver* autofill_driver) : AutofillExternalDelegate(autofill_manager, autofill_driver) {} virtual ~MockAutofillExternalDelegate() {} virtual void DidSelectSuggestion(int identifier) OVERRIDE {} virtual void RemoveSuggestion(const base::string16& value, int identifier) OVERRIDE {} virtual void ClearPreviewedForm() OVERRIDE {} base::WeakPtr GetWeakPtr() { return AutofillExternalDelegate::GetWeakPtr(); } }; class MockAutofillManagerDelegate : public autofill::TestAutofillManagerDelegate { public: MockAutofillManagerDelegate() : prefs_(autofill::test::PrefServiceForTesting()) { } virtual ~MockAutofillManagerDelegate() {} virtual PrefService* GetPrefs() OVERRIDE { return prefs_.get(); } private: scoped_ptr prefs_; DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); }; class TestAutofillPopupController : public AutofillPopupControllerImpl { public: explicit TestAutofillPopupController( base::WeakPtr external_delegate, const gfx::RectF& element_bounds) : AutofillPopupControllerImpl( external_delegate, NULL, NULL, element_bounds, base::i18n::UNKNOWN_DIRECTION) {} virtual ~TestAutofillPopupController() {} void set_display(const gfx::Display display) { display_ = display; } virtual gfx::Display GetDisplayNearestPoint(const gfx::Point& point) const OVERRIDE { return display_; } // Making protected functions public for testing using AutofillPopupControllerImpl::SetPopupBounds; using AutofillPopupControllerImpl::names; using AutofillPopupControllerImpl::subtexts; using AutofillPopupControllerImpl::identifiers; 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::GetNameFontForRow; using AutofillPopupControllerImpl::subtext_font; using AutofillPopupControllerImpl::RowWidthWithoutText; #endif using AutofillPopupControllerImpl::SetValues; using AutofillPopupControllerImpl::GetDesiredPopupWidth; using AutofillPopupControllerImpl::GetDesiredPopupHeight; using AutofillPopupControllerImpl::GetWeakPtr; MOCK_METHOD1(InvalidateRow, void(size_t)); MOCK_METHOD0(UpdateBoundsAndRedrawPopup, void()); MOCK_METHOD0(Hide, void()); void DoHide() { AutofillPopupControllerImpl::Hide(); } private: virtual void ShowView() OVERRIDE {} gfx::Display display_; }; } // namespace class AutofillPopupControllerUnitTest : public ChromeRenderViewHostTestHarness { public: AutofillPopupControllerUnitTest() : manager_delegate_(new MockAutofillManagerDelegate()), autofill_popup_controller_(NULL) {} virtual ~AutofillPopupControllerUnitTest() {} virtual void SetUp() OVERRIDE { ChromeRenderViewHostTestHarness::SetUp(); AutofillDriverImpl::CreateForWebContentsAndDelegate( web_contents(), manager_delegate_.get(), "en-US", AutofillManager::ENABLE_AUTOFILL_DOWNLOAD_MANAGER); AutofillDriverImpl* driver = AutofillDriverImpl::FromWebContents(web_contents()); external_delegate_.reset( new NiceMock( driver->autofill_manager(), driver)); autofill_popup_controller_ = new testing::NiceMock( external_delegate_->GetWeakPtr(),gfx::Rect()); } virtual 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 manager_delegate_; scoped_ptr > external_delegate_; testing::NiceMock* autofill_popup_controller_; }; TEST_F(AutofillPopupControllerUnitTest, SetBounds) { // Ensure the popup size can be set and causes a redraw. gfx::Rect popup_bounds(10, 10, 100, 100); EXPECT_CALL(*autofill_popup_controller_, UpdateBoundsAndRedrawPopup()); popup_controller()->SetPopupBounds(popup_bounds); EXPECT_EQ(popup_bounds, popup_controller()->popup_bounds()); } TEST_F(AutofillPopupControllerUnitTest, ChangeSelectedLine) { // Set up the popup. std::vector names(2, base::string16()); std::vector autofill_ids(2, 0); autofill_popup_controller_->Show(names, names, names, autofill_ids); 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(autofill_popup_controller_->subtexts().size())); // Test wrapping before the front. autofill_popup_controller_->SelectPreviousLine(); EXPECT_EQ(static_cast( autofill_popup_controller_->subtexts().size() - 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 names(2, base::string16()); std::vector autofill_ids(2, 0); autofill_popup_controller_->Show(names, names, names, autofill_ids); // 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 names(3, base::string16()); std::vector autofill_ids; autofill_ids.push_back(1); autofill_ids.push_back(1); autofill_ids.push_back(WebAutofillClient::MenuItemIDAutofillOptions); autofill_popup_controller_->Show(names, names, names, autofill_ids); // 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()); // Try to remove the last entry and ensure it fails (it is an option). autofill_popup_controller_->SetSelectedLine( autofill_popup_controller_->subtexts().size() - 1); EXPECT_FALSE(autofill_popup_controller_->RemoveSelectedLine()); EXPECT_LE(0, autofill_popup_controller_->selected_line()); // 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 names(1, base::string16()); std::vector autofill_ids; autofill_ids.push_back(1); autofill_popup_controller_->Show(names, names, names, autofill_ids); // 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 names(3, base::string16()); std::vector autofill_ids; autofill_ids.push_back(1); autofill_ids.push_back(WebAutofillClient::MenuItemIDSeparator); autofill_ids.push_back(WebAutofillClient::MenuItemIDAutofillOptions); autofill_popup_controller_->Show(names, names, names, autofill_ids); 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, RowWidthWithoutText) { std::vector names(4); std::vector subtexts(4); std::vector icons(4); std::vector ids(4); // Set up some visible display so the text values are kept. gfx::Display display(0, gfx::Rect(0, 0, 100, 100)); autofill_popup_controller_->set_display(display); // Give elements 1 and 3 subtexts and elements 2 and 3 icons, to ensure // all combinations of subtexts and icons. subtexts[1] = ASCIIToUTF16("x"); subtexts[3] = ASCIIToUTF16("x"); icons[2] = ASCIIToUTF16("americanExpressCC"); icons[3] = ASCIIToUTF16("genericCC"); autofill_popup_controller_->Show(names, subtexts, icons, ids); int base_size = AutofillPopupView::kEndPadding * 2 + AutofillPopupView::kBorderThickness * 2; int subtext_increase = AutofillPopupView::kNamePadding; EXPECT_EQ(base_size, autofill_popup_controller_->RowWidthWithoutText(0)); EXPECT_EQ(base_size + subtext_increase, autofill_popup_controller_->RowWidthWithoutText(1)); EXPECT_EQ(base_size + AutofillPopupView::kIconPadding + ui::ResourceBundle::GetSharedInstance().GetImageNamed( IDR_AUTOFILL_CC_AMEX).Width(), autofill_popup_controller_->RowWidthWithoutText(2)); EXPECT_EQ(base_size + subtext_increase + AutofillPopupView::kIconPadding + ui::ResourceBundle::GetSharedInstance().GetImageNamed( IDR_AUTOFILL_CC_GENERIC).Width(), autofill_popup_controller_->RowWidthWithoutText(3)); } TEST_F(AutofillPopupControllerUnitTest, UpdateDataListValues) { std::vector items; items.push_back(base::string16()); std::vector ids; ids.push_back(1); autofill_popup_controller_->Show(items, items, items, ids); EXPECT_EQ(items, autofill_popup_controller_->names()); EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); // Add one data list entry. std::vector data_list_values; data_list_values.push_back(ASCIIToUTF16("data list value 1")); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); // Update the expected values. items.insert(items.begin(), data_list_values[0]); items.insert(items.begin() + 1, base::string16()); ids.insert(ids.begin(), WebAutofillClient::MenuItemIDDataListEntry); ids.insert(ids.begin() + 1, WebAutofillClient::MenuItemIDSeparator); EXPECT_EQ(items, autofill_popup_controller_->names()); EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); // Add two data list entries (which should replace the current one). data_list_values.push_back(ASCIIToUTF16("data list value 2")); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); // Update the expected values. items.insert(items.begin() + 1, data_list_values[1]); ids.insert(ids.begin(), WebAutofillClient::MenuItemIDDataListEntry); EXPECT_EQ(items, autofill_popup_controller_->names()); EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); // Clear all data list values. data_list_values.clear(); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); items.clear(); items.push_back(base::string16()); ids.clear(); ids.push_back(1); EXPECT_EQ(items, autofill_popup_controller_->names()); EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); } TEST_F(AutofillPopupControllerUnitTest, PopupsWithOnlyDataLists) { // Create the popup with a single datalist element. std::vector items; items.push_back(base::string16()); std::vector ids; ids.push_back(WebAutofillClient::MenuItemIDDataListEntry); autofill_popup_controller_->Show(items, items, items, ids); EXPECT_EQ(items, autofill_popup_controller_->names()); EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); // Replace the datalist element with a new one. std::vector data_list_values; data_list_values.push_back(ASCIIToUTF16("data list value 1")); autofill_popup_controller_->UpdateDataListValues(data_list_values, data_list_values); EXPECT_EQ(data_list_values, autofill_popup_controller_->names()); // The id value should stay the same. EXPECT_EQ(ids, autofill_popup_controller_->identifiers()); // 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) { AutofillDriverImpl* driver = AutofillDriverImpl::FromWebContents(web_contents()); MockAutofillExternalDelegate delegate(driver->autofill_manager(), driver); WeakPtr controller = AutofillPopupControllerImpl::GetOrCreate( WeakPtr(), delegate.GetWeakPtr(), NULL, NULL, gfx::Rect(), base::i18n::UNKNOWN_DIRECTION); EXPECT_TRUE(controller.get()); controller->Hide(); controller = AutofillPopupControllerImpl::GetOrCreate( WeakPtr(), delegate.GetWeakPtr(), NULL, NULL, gfx::Rect(), base::i18n::UNKNOWN_DIRECTION); EXPECT_TRUE(controller.get()); WeakPtr controller2 = AutofillPopupControllerImpl::GetOrCreate(controller, delegate.GetWeakPtr(), NULL, NULL, gfx::Rect(), base::i18n::UNKNOWN_DIRECTION); EXPECT_EQ(controller.get(), controller2.get()); controller->Hide(); testing::NiceMock* test_controller = new testing::NiceMock(delegate.GetWeakPtr(), gfx::Rect()); EXPECT_CALL(*test_controller, Hide()); gfx::RectF bounds(0.f, 0.f, 1.f, 2.f); base::WeakPtr controller3 = AutofillPopupControllerImpl::GetOrCreate( test_controller->GetWeakPtr(), delegate.GetWeakPtr(), NULL, NULL, bounds, base::i18n::UNKNOWN_DIRECTION); EXPECT_EQ( bounds, static_cast(controller3.get())-> element_bounds()); controller3->Hide(); // Hide the test_controller to delete it. test_controller->DoHide(); } TEST_F(AutofillPopupControllerUnitTest, ProperlyResetController) { std::vector names(2); std::vector ids(2); popup_controller()->SetValues(names, names, names, ids); popup_controller()->SetSelectedLine(0); // Now show a new popup with the same controller, but with fewer items. WeakPtr controller = AutofillPopupControllerImpl::GetOrCreate( popup_controller()->GetWeakPtr(), delegate()->GetWeakPtr(), NULL, NULL, gfx::Rect(), base::i18n::UNKNOWN_DIRECTION); EXPECT_NE(0, controller->selected_line()); EXPECT_TRUE(controller->names().empty()); } #if !defined(OS_ANDROID) TEST_F(AutofillPopupControllerUnitTest, ElideText) { std::vector names; names.push_back(ASCIIToUTF16("Text that will need to be trimmed")); names.push_back(ASCIIToUTF16("Untrimmed")); std::vector subtexts; subtexts.push_back(ASCIIToUTF16("Label that will be trimmed")); subtexts.push_back(ASCIIToUTF16("Untrimmed")); std::vector icons(2, ASCIIToUTF16("genericCC")); std::vector autofill_ids(2, 0); // Show the popup once so we can easily generate the size it needs. autofill_popup_controller_->Show(names, subtexts, icons, autofill_ids); // Ensure the popup will be too small to display all of the first row. int popup_max_width = autofill_popup_controller_->GetNameFontForRow(0).GetStringWidth( names[0]) + autofill_popup_controller_->subtext_font().GetStringWidth(subtexts[0]) - 25; gfx::Rect popup_bounds = gfx::Rect(0, 0, popup_max_width, 0); autofill_popup_controller_->set_display(gfx::Display(0, popup_bounds)); autofill_popup_controller_->Show(names, subtexts, icons, autofill_ids); // The first element was long so it should have been trimmed. EXPECT_NE(names[0], autofill_popup_controller_->names()[0]); EXPECT_NE(subtexts[0], autofill_popup_controller_->subtexts()[0]); // The second element was shorter so it should be unchanged. EXPECT_EQ(names[1], autofill_popup_controller_->names()[1]); EXPECT_EQ(subtexts[1], autofill_popup_controller_->subtexts()[1]); } #endif TEST_F(AutofillPopupControllerUnitTest, GrowPopupInSpace) { std::vector names(1); std::vector autofill_ids(1, 1); // Call Show so that GetDesired...() will be able to provide valid values. autofill_popup_controller_->Show(names, names, names, autofill_ids); int desired_width = autofill_popup_controller_->GetDesiredPopupWidth(); int desired_height = autofill_popup_controller_->GetDesiredPopupHeight(); // Set up the visible screen space. gfx::Display display(0, gfx::Rect(0, 0, desired_width * 2, desired_height * 2)); // Store the possible element bounds and the popup bounds they should result // in. std::vector element_bounds; std::vector expected_popup_bounds; // The popup grows down and to the right. element_bounds.push_back(gfx::RectF(0, 0, 0, 0)); expected_popup_bounds.push_back( gfx::Rect(0, 0, desired_width, desired_height)); // The popup grows down and to the left. element_bounds.push_back(gfx::RectF(2 * desired_width, 0, 0, 0)); expected_popup_bounds.push_back( gfx::Rect(desired_width, 0, desired_width, desired_height)); // The popup grows up and to the right. element_bounds.push_back(gfx::RectF(0, 2 * desired_height, 0, 0)); expected_popup_bounds.push_back( gfx::Rect(0, desired_height, desired_width, desired_height)); // The popup grows up and to the left. element_bounds.push_back( gfx::RectF(2 * desired_width, 2 * desired_height, 0, 0)); expected_popup_bounds.push_back( gfx::Rect(desired_width, desired_height, desired_width, desired_height)); // The popup would be partial off the top and left side of the screen. element_bounds.push_back( gfx::RectF(-desired_width / 2, -desired_height / 2, 0, 0)); expected_popup_bounds.push_back( gfx::Rect(0, 0, desired_width, desired_height)); // The popup would be partially off the bottom and the right side of // the screen. element_bounds.push_back( gfx::RectF(desired_width * 1.5, desired_height * 1.5, 0, 0)); expected_popup_bounds.push_back( gfx::Rect((desired_width + 1) / 2, (desired_height + 1) / 2, desired_width, desired_height)); for (size_t i = 0; i < element_bounds.size(); ++i) { AutofillDriverImpl* driver = AutofillDriverImpl::FromWebContents(web_contents()); NiceMock external_delegate( driver->autofill_manager(), driver); TestAutofillPopupController* autofill_popup_controller = new TestAutofillPopupController(external_delegate.GetWeakPtr(), element_bounds[i]); autofill_popup_controller->set_display(display); autofill_popup_controller->Show(names, names, names, autofill_ids); EXPECT_EQ(expected_popup_bounds[i].ToString(), autofill_popup_controller->popup_bounds().ToString()) << "Popup bounds failed to match for test " << i; // Hide the controller to delete it. autofill_popup_controller->DoHide(); } } } // namespace autofill