blob: 03cc4881567a172371eea8497b86c3b645450068 [file] [log] [blame]
// Copyright 2014 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 "components/omnibox/browser/omnibox_popup_model.h"
#include <stddef.h>
#include <memory>
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/omnibox/browser/autocomplete_controller.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/omnibox_edit_model.h"
#include "components/omnibox/browser/omnibox_popup_view.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/test_omnibox_client.h"
#include "components/omnibox/browser/test_omnibox_edit_controller.h"
#include "components/omnibox/browser/test_omnibox_view.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace {
class TestOmniboxPopupView : public OmniboxPopupView {
public:
~TestOmniboxPopupView() override {}
bool IsOpen() const override { return false; }
void InvalidateLine(size_t line) override {}
void UpdatePopupAppearance() override {}
void ProvideButtonFocusHint(size_t line) override {}
void OnMatchIconUpdated(size_t match_index) override {}
void OnDragCanceled() override {}
};
class TestOmniboxEditModel : public OmniboxEditModel {
public:
TestOmniboxEditModel(OmniboxView* view,
OmniboxEditController* controller,
std::unique_ptr<OmniboxClient> client)
: OmniboxEditModel(view, controller, std::move(client)) {}
bool PopupIsOpen() const override { return true; }
const base::string16& text() const { return text_; }
bool is_temporary_text() const { return is_temporary_text_; }
void OnPopupDataChanged(const base::string16& temporary_text,
bool is_temporary_text,
const base::string16& inline_autocompletion,
const base::string16& prefix_autocompletion,
const SplitAutocompletion& split_autocompletion,
const base::string16& keyword,
bool is_keyword_hint,
const base::string16& additional_text) override {
OmniboxEditModel::OnPopupDataChanged(
temporary_text, is_temporary_text, inline_autocompletion,
prefix_autocompletion, split_autocompletion, keyword, is_keyword_hint,
additional_text);
text_ = is_temporary_text ? temporary_text : inline_autocompletion;
is_temporary_text_ = is_temporary_text;
}
private:
// Contains the most recent text passed by the popup model to the edit model.
base::string16 text_;
bool is_temporary_text_ = false;
};
} // namespace
using Selection = OmniboxPopupModel::Selection;
class OmniboxPopupModelTest : public ::testing::Test {
public:
OmniboxPopupModelTest()
: view_(&controller_),
model_(&view_, &controller_, std::make_unique<TestOmniboxClient>()),
popup_model_(&popup_view_, &model_, &pref_service_) {
omnibox::RegisterProfilePrefs(pref_service_.registry());
}
OmniboxPopupModelTest(const OmniboxPopupModelTest&) = delete;
OmniboxPopupModelTest& operator=(const OmniboxPopupModelTest&) = delete;
TestingPrefServiceSimple* pref_service() { return &pref_service_; }
TestOmniboxEditModel* model() { return &model_; }
OmniboxPopupModel* popup_model() { return &popup_model_; }
private:
base::test::TaskEnvironment task_environment_;
TestOmniboxEditController controller_;
TestingPrefServiceSimple pref_service_;
TestOmniboxView view_;
TestOmniboxEditModel model_;
TestOmniboxPopupView popup_view_;
OmniboxPopupModel popup_model_;
};
// A test fixture that enables the #omnibox-suggestion-button-row field trial.
class OmniboxPopupModelSuggestionButtonRowTest : public OmniboxPopupModelTest {
public:
OmniboxPopupModelSuggestionButtonRowTest() = default;
OmniboxPopupModelSuggestionButtonRowTest(
const OmniboxPopupModelSuggestionButtonRowTest&) = delete;
OmniboxPopupModelSuggestionButtonRowTest& operator=(
const OmniboxPopupModelSuggestionButtonRowTest&) = delete;
protected:
// testing::Test:
void SetUp() override {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitAndEnableFeature(
omnibox::kOmniboxSuggestionButtonRow);
OmniboxPopupModelTest::SetUp();
}
void TearDown() override { scoped_feature_list_.reset(); }
void InitKeywordButtonFeature() {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitWithFeatures(
{omnibox::kOmniboxSuggestionButtonRow,
omnibox::kOmniboxKeywordSearchButton},
{});
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};
// This verifies that the new treatment of the user's selected match in
// |SetSelectedLine()| with removed |AutocompleteResult::Selection::empty()|
// is correct in the face of various replacement versions of |empty()|.
TEST_F(OmniboxPopupModelTest, SetSelectedLine) {
ACMatches matches;
for (size_t i = 0; i < 2; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_TRUE(popup_model()->SelectionOnInitialLine());
popup_model()->SetSelection(OmniboxPopupModel::Selection(0), true, false);
EXPECT_TRUE(popup_model()->SelectionOnInitialLine());
popup_model()->SetSelection(OmniboxPopupModel::Selection(0), false, false);
EXPECT_TRUE(popup_model()->SelectionOnInitialLine());
}
TEST_F(OmniboxPopupModelTest, SetSelectedLineWithNoDefaultMatches) {
// Creates a set of matches with NO matches allowed to be default.
ACMatches matches;
for (size_t i = 0; i < 2; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
matches.push_back(match);
}
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(OmniboxPopupModel::kNoMatch, popup_model()->selected_line());
EXPECT_TRUE(popup_model()->SelectionOnInitialLine());
popup_model()->SetSelection(OmniboxPopupModel::Selection(0), false, false);
EXPECT_EQ(0U, popup_model()->selected_line());
EXPECT_FALSE(popup_model()->SelectionOnInitialLine());
popup_model()->SetSelection(OmniboxPopupModel::Selection(1), false, false);
EXPECT_EQ(1U, popup_model()->selected_line());
EXPECT_FALSE(popup_model()->SelectionOnInitialLine());
popup_model()->ResetToInitialState();
EXPECT_EQ(OmniboxPopupModel::kNoMatch, popup_model()->selected_line());
EXPECT_TRUE(popup_model()->SelectionOnInitialLine());
}
TEST_F(OmniboxPopupModelTest, PopupPositionChanging) {
ACMatches matches;
for (size_t i = 0; i < 3; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Test moving and wrapping down.
for (size_t n : {1, 2, 0}) {
model()->OnUpOrDownKeyPressed(1);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// And down.
for (size_t n : {2, 1, 0}) {
model()->OnUpOrDownKeyPressed(-1);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
}
TEST_F(OmniboxPopupModelTest, PopupStepSelection) {
ACMatches matches;
for (size_t i = 0; i < 4; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
// Make match index 1 deletable to verify we can step to that.
matches[1].deletable = true;
// Make match index 2 have an associated keyword for irregular state stepping.
// Make it deleteable also to verify that we correctly handle matches with
// keywords that are ALSO deleteable (this is an edge case that was broken).
matches[2].associated_keyword =
std::make_unique<AutocompleteMatch>(matches.back());
matches[2].deletable = true;
// Make match index 3 have a suggestion_group_id to test header behavior.
matches[3].suggestion_group_id = 7;
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->MergeHeadersMap({{7, base::UTF8ToUTF16("header")}});
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Step by lines forward.
for (size_t n : {1, 2, 3, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by lines backward.
for (size_t n : {3, 2, 1, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by states forward.
for (auto selection : {
Selection(1, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(2, OmniboxPopupModel::KEYWORD_MODE),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Step by states backward.
// Note the lack of KEYWORD. This is by design. Stepping forward
// should land on KEYWORD, but stepping backward should not.
for (auto selection : {
Selection(3, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(1, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(2, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Try the kAllLines step behavior.
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(0, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(3, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
}
TEST_F(OmniboxPopupModelTest, PopupStepSelectionWithHiddenGroupIds) {
ACMatches matches;
for (size_t i = 0; i < 4; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
// Hide the second two matches.
matches[2].suggestion_group_id = 7;
matches[3].suggestion_group_id = 7;
omnibox::SetSuggestionGroupVisibility(
pref_service(), 7, omnibox::SuggestionGroupVisibility::HIDDEN);
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->MergeHeadersMap({{7, base::UTF8ToUTF16("header")}});
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Test the simple kAllLines case.
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(1u, model()->popup_model()->selected_line());
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Test the kStateOrLine case, forwards and backwards.
for (auto selection : {
Selection(1, OmniboxPopupModel::NORMAL),
Selection(2, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(0, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
for (auto selection : {
Selection(2, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(1, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Test the kWholeLine case, forwards and backwards.
for (auto selection : {
Selection(0, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
for (auto selection : {
Selection(0, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
}
TEST_F(OmniboxPopupModelSuggestionButtonRowTest,
PopupStepSelectionWithButtonRow) {
ACMatches matches;
for (size_t i = 0; i < 4; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
// Make match index 1 have a tab match and be deletable to verify we can step
// to each.
matches[1].has_tab_match = true;
matches[1].deletable = true;
// Make match index 2 have an associated keyword for irregular state stepping.
// Make it deleteable and with a tab match to verify that we correctly skip
// those on matches with an associated keyword.
matches[2].associated_keyword =
std::make_unique<AutocompleteMatch>(matches.back());
matches[2].deletable = true;
matches[2].has_tab_match = true;
// Make match index 3 have a suggestion_group_id to test header behavior.
matches[3].suggestion_group_id = 7;
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->MergeHeadersMap({{7, base::UTF8ToUTF16("header")}});
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Step by lines forward.
for (size_t n : {1, 2, 3, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by lines backward.
for (size_t n : {3, 2, 1, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by states forward.
for (auto selection : {
Selection(1, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(2, OmniboxPopupModel::KEYWORD_MODE),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Step by states backward.
// Note the lack of KEYWORD. This is by design. Stepping forward
// should land on KEYWORD, but stepping backward should not.
for (auto selection : {
Selection(3, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH),
Selection(1, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(2, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Try the kAllLines step behavior.
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(0, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(3, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
}
TEST_F(OmniboxPopupModelSuggestionButtonRowTest,
PopupStepSelectionWithButtonRowAndKeywordButton) {
InitKeywordButtonFeature();
ACMatches matches;
for (size_t i = 0; i < 5; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.keyword = base::ASCIIToUTF16("match");
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
// Make match index 1 deletable to verify we can step to that.
matches[1].deletable = true;
// Make match index 2 only have an associated keyword to verify we can step
// backwards into keyword search mode.
matches[2].associated_keyword =
std::make_unique<AutocompleteMatch>(matches.back());
// Make match index 3 have an associated keyword, tab match, and deletable to
// verify keyword mode doesn't override tab match and remove suggestion
// buttons (as it does with button row disabled)
matches[3].associated_keyword =
std::make_unique<AutocompleteMatch>(matches.back());
matches[3].has_tab_match = true;
matches[3].deletable = true;
// Make match index 4 have a suggestion_group_id to test header behavior.
matches[4].suggestion_group_id = 7;
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->MergeHeadersMap({{7, base::UTF8ToUTF16("header")}});
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0u, model()->popup_model()->selected_line());
// Step by lines forward.
for (size_t n : {1, 2, 3, 4, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by lines backward.
for (size_t n : {4, 3, 2, 1, 0}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kWholeLine);
EXPECT_EQ(n, model()->popup_model()->selected_line());
}
// Step by states forward.
for (auto selection : {
Selection(1, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(2, OmniboxPopupModel::KEYWORD_MODE),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(3, OmniboxPopupModel::KEYWORD_MODE),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(4, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(4, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Step by states backward. Unlike without suggestion button row, there is no
// difference in behavior for KEYWORD mode moving forward or backward.
for (auto selection : {
Selection(4, OmniboxPopupModel::NORMAL),
Selection(4, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH),
Selection(3, OmniboxPopupModel::KEYWORD_MODE),
Selection(3, OmniboxPopupModel::NORMAL),
Selection(2, OmniboxPopupModel::KEYWORD_MODE),
Selection(2, OmniboxPopupModel::NORMAL),
Selection(1, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
Selection(1, OmniboxPopupModel::NORMAL),
Selection(0, OmniboxPopupModel::NORMAL),
Selection(4, OmniboxPopupModel::NORMAL),
Selection(4, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
Selection(3, OmniboxPopupModel::FOCUSED_BUTTON_REMOVE_SUGGESTION),
}) {
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(selection, model()->popup_model()->selection());
}
// Try the kAllLines step behavior.
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(0, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kAllLines);
EXPECT_EQ(Selection(4, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
}
TEST_F(OmniboxPopupModelTest, PopupInlineAutocompleteAndTemporaryText) {
// Create a set of three matches "a|1" (inline autocompleted), "a2", "a3".
// The third match has a suggestion group ID.
ACMatches matches;
for (size_t i = 0; i < 3; ++i) {
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::SEARCH_SUGGEST);
match.allowed_to_be_default_match = true;
matches.push_back(match);
}
matches[0].fill_into_edit = base::UTF8ToUTF16("a1");
matches[0].inline_autocompletion = base::UTF8ToUTF16("1");
matches[1].fill_into_edit = base::UTF8ToUTF16("a2");
matches[2].fill_into_edit = base::UTF8ToUTF16("a3");
matches[2].suggestion_group_id = 7;
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("a"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->MergeHeadersMap({{7, base::UTF8ToUTF16("header")}});
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
// Simulate OmniboxController updating the popup, then check initial state.
model()->OnPopupDataChanged(base::string16(),
/*is_temporary_text=*/false,
base::UTF8ToUTF16("1"), base::string16(), {},
base::string16(), false, base::string16());
EXPECT_EQ(Selection(0, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
EXPECT_EQ(base::UTF8ToUTF16("1"), model()->text());
EXPECT_FALSE(model()->is_temporary_text());
// Tab down to second match.
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(Selection(1, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
EXPECT_EQ(base::UTF8ToUTF16("a2"), model()->text());
EXPECT_TRUE(model()->is_temporary_text());
// Tab down to header above the third match, expect that we have an empty
// string for our temporary text.
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(Selection(2, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
model()->popup_model()->selection());
EXPECT_EQ(base::string16(), model()->text());
EXPECT_TRUE(model()->is_temporary_text());
// Now tab down to the third match, and expect that we update the temporary
// text to the third match.
popup_model()->StepSelection(OmniboxPopupModel::kForward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(Selection(2, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
EXPECT_EQ(base::UTF8ToUTF16("a3"), model()->text());
EXPECT_TRUE(model()->is_temporary_text());
// Now tab backwards to the header again, expect that we have an empty string
// for our temporary text.
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(Selection(2, OmniboxPopupModel::FOCUSED_BUTTON_HEADER),
model()->popup_model()->selection());
EXPECT_EQ(base::string16(), model()->text());
EXPECT_TRUE(model()->is_temporary_text());
// Now tab backwards to the second match, expect we update the temporary text
// to the second match.
popup_model()->StepSelection(OmniboxPopupModel::kBackward,
OmniboxPopupModel::kStateOrLine);
EXPECT_EQ(Selection(1, OmniboxPopupModel::NORMAL),
model()->popup_model()->selection());
EXPECT_EQ(base::UTF8ToUTF16("a2"), model()->text());
EXPECT_TRUE(model()->is_temporary_text());
}
TEST_F(OmniboxPopupModelTest, ComputeMatchMaxWidths) {
int contents_max_width, description_max_width;
const int separator_width = 10;
const int kMinimumContentsWidth = 300;
int contents_width, description_width, available_width;
// Both contents and description fit fine.
contents_width = 100;
description_width = 50;
available_width = 200;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ(description_width, description_max_width);
// Contents should be given as much space as it wants up to 300 pixels.
contents_width = 100;
description_width = 50;
available_width = 100;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ(0, description_max_width);
// Description should be hidden if it's at least 75 pixels wide but doesn't
// get 75 pixels of space.
contents_width = 300;
description_width = 100;
available_width = 384;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ(0, description_max_width);
// If contents and description are on separate lines, each can take the full
// available width.
contents_width = 300;
description_width = 100;
available_width = 384;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width, true,
true, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ(description_width, description_max_width);
// Both contents and description will be limited.
contents_width = 310;
description_width = 150;
available_width = 400;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(kMinimumContentsWidth, contents_max_width);
EXPECT_EQ(available_width - kMinimumContentsWidth - separator_width,
description_max_width);
// Contents takes all available space.
contents_width = 400;
description_width = 0;
available_width = 200;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(available_width, contents_max_width);
EXPECT_EQ(0, description_max_width);
// Large contents will be truncated but small description won't if two line
// suggestion.
contents_width = 400;
description_width = 100;
available_width = 200;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width, true,
true, &contents_max_width, &description_max_width);
EXPECT_EQ(available_width, contents_max_width);
EXPECT_EQ(description_width, description_max_width);
// Large description will be truncated but small contents won't if two line
// suggestion.
contents_width = 100;
description_width = 400;
available_width = 200;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width, true,
true, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ(available_width, description_max_width);
// Half and half.
contents_width = 395;
description_width = 395;
available_width = 700;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(345, contents_max_width);
EXPECT_EQ(345, description_max_width);
// When we disallow shrinking the contents, it should get as much space as
// it wants.
contents_width = 395;
description_width = 395;
available_width = 700;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, false, &contents_max_width, &description_max_width);
EXPECT_EQ(contents_width, contents_max_width);
EXPECT_EQ((available_width - contents_width - separator_width),
description_max_width);
// (available_width - separator_width) is odd, so contents gets the extra
// pixel.
contents_width = 395;
description_width = 395;
available_width = 699;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(345, contents_max_width);
EXPECT_EQ(344, description_max_width);
// Not enough space to draw anything.
contents_width = 1;
description_width = 1;
available_width = 0;
OmniboxPopupModel::ComputeMatchMaxWidths(
contents_width, separator_width, description_width, available_width,
false, true, &contents_max_width, &description_max_width);
EXPECT_EQ(0, contents_max_width);
EXPECT_EQ(0, description_max_width);
}
// Makes sure focus remains on the tab switch button when nothing changes,
// and leaves when it does. Exercises the ratcheting logic in
// OmniboxPopupModel::OnResultChanged().
TEST_F(OmniboxPopupModelTest, TestFocusFixing) {
ACMatches matches;
AutocompleteMatch match(nullptr, 1000, false,
AutocompleteMatchType::URL_WHAT_YOU_TYPED);
match.contents = base::ASCIIToUTF16("match1.com");
match.destination_url = GURL("http://match1.com");
match.allowed_to_be_default_match = true;
match.has_tab_match = true;
matches.push_back(match);
auto* result = &model()->autocomplete_controller()->result_;
AutocompleteInput input(base::UTF8ToUTF16("match"),
metrics::OmniboxEventProto::NTP,
TestSchemeClassifier());
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
popup_model()->SetSelection(OmniboxPopupModel::Selection(0), true, false);
// The default state should be unfocused.
EXPECT_EQ(OmniboxPopupModel::NORMAL, popup_model()->selected_line_state());
// Focus the selection.
popup_model()->SetSelection(OmniboxPopupModel::Selection(0));
popup_model()->SetSelectedLineState(
OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH);
EXPECT_EQ(OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH,
popup_model()->selected_line_state());
// Adding a match at end won't change that we selected first suggestion, so
// shouldn't change focused state.
matches[0].relevance = 999;
// Give it a different name so not deduped.
matches[0].contents = base::ASCIIToUTF16("match2.com");
matches[0].destination_url = GURL("http://match2.com");
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH,
popup_model()->selected_line_state());
// Changing selection should change focused state.
popup_model()->SetSelection(OmniboxPopupModel::Selection(1));
EXPECT_EQ(OmniboxPopupModel::NORMAL, popup_model()->selected_line_state());
// Adding a match at end will reset selection to first, so should change
// selected line, and thus focus.
popup_model()->SetSelectedLineState(
OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH);
matches[0].relevance = 999;
matches[0].contents = base::ASCIIToUTF16("match3.com");
matches[0].destination_url = GURL("http://match3.com");
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0U, popup_model()->selected_line());
EXPECT_EQ(OmniboxPopupModel::NORMAL, popup_model()->selected_line_state());
// Prepending a match won't change selection, but since URL is different,
// should clear the focus state.
popup_model()->SetSelectedLineState(
OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH);
matches[0].relevance = 1100;
matches[0].contents = base::ASCIIToUTF16("match4.com");
matches[0].destination_url = GURL("http://match4.com");
result->AppendMatches(input, matches);
result->SortAndCull(input, nullptr);
popup_model()->OnResultChanged();
EXPECT_EQ(0U, popup_model()->selected_line());
EXPECT_EQ(OmniboxPopupModel::NORMAL, popup_model()->selected_line_state());
// Selecting |kNoMatch| should clear focus.
popup_model()->SetSelectedLineState(
OmniboxPopupModel::FOCUSED_BUTTON_TAB_SWITCH);
popup_model()->SetSelection(
OmniboxPopupModel::Selection(OmniboxPopupModel::kNoMatch));
popup_model()->OnResultChanged();
EXPECT_EQ(OmniboxPopupModel::NORMAL, popup_model()->selected_line_state());
}