blob: 1d480c7827d79a603093177db7746f309568ab43 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/ui/app_list/search/mixer.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <vector>
#include "ash/public/cpp/app_list/app_list_constants.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/app_list/search/search_provider.h"
#include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
#include "testing/gtest/include/gtest/gtest.h"
class FakeAppListModelUpdater;
namespace app_list {
namespace test {
// Maximum number of results to show in each mixer group.
const size_t kMaxAppsGroupResults = 4;
const size_t kMaxOmniboxResults = 4;
const size_t kMaxWebstoreResults = 2;
class TestSearchResult : public ChromeSearchResult {
public:
TestSearchResult(const std::string& id, double relevance)
: instance_id_(instantiation_count++) {
set_id(id);
SetTitle(base::UTF8ToUTF16(id));
set_relevance(relevance);
}
~TestSearchResult() override {}
// ChromeSearchResult overrides:
void Open(int event_flags) override {}
void InvokeAction(int action_index, int event_flags) override {}
// For reference equality testing. (Addresses cannot be used to test reference
// equality because it is possible that an object will be allocated at the
// same address as a previously deleted one.)
static int GetInstanceId(ChromeSearchResult* result) {
return static_cast<const TestSearchResult*>(result)->instance_id_;
}
private:
static int instantiation_count;
int instance_id_;
DISALLOW_COPY_AND_ASSIGN(TestSearchResult);
};
int TestSearchResult::instantiation_count = 0;
class TestSearchProvider : public SearchProvider {
public:
explicit TestSearchProvider(const std::string& prefix)
: prefix_(prefix),
count_(0),
bad_relevance_range_(false),
display_type_(ash::SearchResultDisplayType::kList) {}
~TestSearchProvider() override {}
// SearchProvider overrides:
void Start(const base::string16& query) override {
ClearResults();
for (size_t i = 0; i < count_; ++i) {
const std::string id =
base::StringPrintf("%s%d", prefix_.c_str(), static_cast<int>(i));
double relevance = 1.0 - i / 10.0;
// If bad_relevance_range_, change the relevances to give results outside
// of the canonical [0.0, 1.0] range.
if (bad_relevance_range_)
relevance = 10.0 - i * 10;
TestSearchResult* result = new TestSearchResult(id, relevance);
result->SetDisplayType(display_type_);
Add(std::unique_ptr<ChromeSearchResult>(result));
}
}
void set_prefix(const std::string& prefix) { prefix_ = prefix; }
void SetDisplayType(ChromeSearchResult::DisplayType display_type) {
display_type_ = display_type;
}
void set_count(size_t count) { count_ = count; }
void set_bad_relevance_range() { bad_relevance_range_ = true; }
private:
std::string prefix_;
size_t count_;
bool bad_relevance_range_;
ChromeSearchResult::DisplayType display_type_;
DISALLOW_COPY_AND_ASSIGN(TestSearchProvider);
};
class MixerTest : public testing::Test {
public:
MixerTest() {}
~MixerTest() override {}
// testing::Test overrides:
void SetUp() override {
model_updater_ = std::make_unique<FakeAppListModelUpdater>();
providers_.push_back(std::make_unique<TestSearchProvider>("app"));
providers_.push_back(std::make_unique<TestSearchProvider>("omnibox"));
providers_.push_back(std::make_unique<TestSearchProvider>("webstore"));
mixer_ = std::make_unique<Mixer>(model_updater_.get());
// TODO(warx): when fullscreen app list is default enabled, modify this test
// to test answer card/apps group having relevance boost.
size_t apps_group_id = mixer_->AddGroup(kMaxAppsGroupResults, 1.0, 0.0);
size_t omnibox_group_id = mixer_->AddGroup(kMaxOmniboxResults, 1.0, 0.0);
size_t webstore_group_id = mixer_->AddGroup(kMaxWebstoreResults, 0.5, 0.0);
mixer_->AddProviderToGroup(apps_group_id, providers_[0].get());
mixer_->AddProviderToGroup(omnibox_group_id, providers_[1].get());
mixer_->AddProviderToGroup(webstore_group_id, providers_[2].get());
}
void RunQuery() {
const base::string16 query;
for (size_t i = 0; i < providers_.size(); ++i)
providers_[i]->Start(query);
mixer_->MixAndPublish(kMaxSearchResults);
}
std::string GetResults() const {
auto& results = model_updater_->search_results();
std::string result;
for (size_t i = 0; i < results.size(); ++i) {
if (!result.empty())
result += ',';
result += base::UTF16ToUTF8(results[i]->title());
}
return result;
}
Mixer* mixer() { return mixer_.get(); }
TestSearchProvider* app_provider() { return providers_[0].get(); }
TestSearchProvider* omnibox_provider() { return providers_[1].get(); }
TestSearchProvider* webstore_provider() { return providers_[2].get(); }
private:
std::unique_ptr<Mixer> mixer_;
std::unique_ptr<FakeAppListModelUpdater> model_updater_;
std::vector<std::unique_ptr<TestSearchProvider>> providers_;
DISALLOW_COPY_AND_ASSIGN(MixerTest);
};
TEST_F(MixerTest, Basic) {
// Note: Some cases in |expected| have vastly more results than others, due to
// the "at least 6" mechanism. If it gets at least 6 results from all
// providers, it stops at 6. If not, it fetches potentially many more results
// from all providers. Not ideal, but currently by design.
struct TestCase {
const size_t app_results;
const size_t omnibox_results;
const size_t webstore_results;
const char* expected;
} kTestCases[] = {
{0, 0, 0, ""},
{10, 0, 0, "app0,app1,app2,app3,app4,app5,app6,app7,app8,app9"},
{0, 0, 10,
"webstore0,webstore1,webstore2,webstore3,webstore4,webstore5,webstore6,"
"webstore7,webstore8,webstore9"},
{4, 6, 0, "app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3"},
{4, 6, 2,
"app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3,webstore0,"
"webstore1"},
{10, 10, 10,
"app0,omnibox0,app1,omnibox1,app2,omnibox2,app3,omnibox3,webstore0,"
"webstore1"},
{0, 10, 0,
"omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,omnibox5,omnibox6,"
"omnibox7,omnibox8,omnibox9"},
{0, 10, 1,
"omnibox0,omnibox1,omnibox2,omnibox3,webstore0,omnibox4,omnibox5,"
"omnibox6,omnibox7,omnibox8,omnibox9"},
{0, 10, 2, "omnibox0,omnibox1,omnibox2,omnibox3,webstore0,webstore1"},
{1, 10, 0,
"app0,omnibox0,omnibox1,omnibox2,omnibox3,omnibox4,omnibox5,omnibox6,"
"omnibox7,omnibox8,omnibox9"},
{2, 10, 0, "app0,omnibox0,app1,omnibox1,omnibox2,omnibox3"},
{2, 10, 1, "app0,omnibox0,app1,omnibox1,omnibox2,omnibox3,webstore0"},
{2, 10, 2,
"app0,omnibox0,app1,omnibox1,omnibox2,omnibox3,webstore0,webstore1"},
{2, 0, 2, "app0,app1,webstore0,webstore1"},
{0, 0, 0, ""},
};
for (size_t i = 0; i < base::size(kTestCases); ++i) {
app_provider()->set_count(kTestCases[i].app_results);
omnibox_provider()->set_count(kTestCases[i].omnibox_results);
webstore_provider()->set_count(kTestCases[i].webstore_results);
RunQuery();
EXPECT_EQ(kTestCases[i].expected, GetResults()) << "Case " << i;
}
}
TEST_F(MixerTest, RemoveDuplicates) {
const std::string dup = "dup";
// This gives "dup0,dup1,dup2".
app_provider()->set_prefix(dup);
app_provider()->set_count(3);
// This gives "dup0,dup1".
omnibox_provider()->set_prefix(dup);
omnibox_provider()->set_count(2);
// This gives "dup0".
webstore_provider()->set_prefix(dup);
webstore_provider()->set_count(1);
RunQuery();
// Only three results with unique id are kept.
EXPECT_EQ("dup0,dup1,dup2", GetResults());
}
} // namespace test
} // namespace app_list