chromium/chrome/browser/ash/app_list/search/games/game_provider_unittest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/app_list/search/games/game_provider.h"

#include "ash/constants/ash_pref_names.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/browser/apps/app_discovery_service/app_discovery_util.h"
#include "chrome/browser/apps/app_discovery_service/game_extras.h"
#include "chrome/browser/apps/app_discovery_service/result.h"
#include "chrome/browser/ash/app_list/search/search_features.h"
#include "chrome/browser/ash/app_list/search/test/test_search_controller.h"
#include "chrome/browser/ash/app_list/test/test_app_list_controller_delegate.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace app_list::test {
namespace {

using ::testing::ElementsAre;
using ::testing::UnorderedElementsAre;

MATCHER_P(Title, title, "") {
  return arg->title() == title;
}

apps::Result MakeAppsResult(const std::u16string& title,
                            const std::u16string& source) {
  return apps::Result(
      apps::AppSource::kGames, "12345", title,
      std::make_unique<apps::GameExtras>(
          source, base::FilePath("/icons/test.png"),
          /*is_icon_masking_allowed=*/false, GURL("https://game.com/game")));
}

apps::Result MakeAppsResult(const std::u16string& title) {
  return MakeAppsResult(title, u"SourceName");
}

// Checks that the result's details text vector contains exactly one text item
// with the given text.
bool DetailsEquals(const std::unique_ptr<ChromeSearchResult>& result,
                   const std::u16string& details) {
  const auto& details_vector = result->details_text_vector();

  if (details_vector.size() != 1u)
    return false;

  if (details_vector[0].GetType() != ash::SearchResultTextItemType::kString)
    return false;

  return details_vector[0].GetText() == details;
}

}  // namespace

// Parameterized by the ItemSuggest "enabled_override" parameter.
class GameProviderTest : public testing::Test,
                         public testing::WithParamInterface<bool> {
 public:
  GameProviderTest() {
    bool enabled_override = GetParam();
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {search_features::kLauncherGameSearch,
         {{"enabled_override", enabled_override ? "true" : "false"}}}};
    feature_list_.InitWithFeaturesAndParameters(
        enabled_features, std::vector<base::test::FeatureRef>());
  }

 protected:
  void SetUp() override {
    profile_ = std::make_unique<TestingProfile>();
    auto provider =
        std::make_unique<GameProvider>(profile_.get(), &list_controller_);
    provider_ = provider.get();

    search_controller_ = std::make_unique<TestSearchController>();
    search_controller_->AddProvider(std::move(provider));
  }

  const SearchProvider::Results& LastResults() {
    return search_controller_->last_results();
  }

  void SetUpTestingIndex() {
    GameProvider::GameIndex index;
    index.push_back(MakeAppsResult(u"First Title"));
    index.push_back(MakeAppsResult(u"Second Title"));
    index.push_back(MakeAppsResult(u"Third Title"));
    provider_->SetGameIndexForTest(std::move(index));
  }

  void StartSearch(const std::u16string& query) {
    search_controller_->StartSearch(query);
    task_environment_.RunUntilIdle();
  }

  GameProvider* provider() const { return provider_; }

  base::test::ScopedFeatureList feature_list_;
  content::BrowserTaskEnvironment task_environment_;
  ::test::TestAppListControllerDelegate list_controller_;
  std::unique_ptr<TestSearchController> search_controller_;
  std::unique_ptr<Profile> profile_;

  raw_ptr<GameProvider> provider_ = nullptr;
};

INSTANTIATE_TEST_SUITE_P(ProductivityLauncher,
                         GameProviderTest,
                         testing::Bool());

TEST_P(GameProviderTest, SearchResultsMatchQuery) {
  SetUpTestingIndex();

  StartSearch(u"first");
  EXPECT_THAT(LastResults(), ElementsAre(Title(u"First Title")));

  StartSearch(u"title");
  EXPECT_THAT(LastResults(), UnorderedElementsAre(Title(u"First Title"),
                                                  Title(u"Second Title"),
                                                  Title(u"Third Title")));
}

// Tests that scores are not greatly affected by characters such as apostrophe.
TEST_P(GameProviderTest, SpecialCharactersIgnored) {
  GameProvider::GameIndex index;
  index.push_back(MakeAppsResult(u"titles one"));
  index.push_back(MakeAppsResult(u"title's one"));
  provider()->SetGameIndexForTest(std::move(index));

  // Expect that the results have similar scores.
  StartSearch(u"titles");
  ASSERT_EQ(LastResults().size(), 2u);
  double score_diff =
      abs(LastResults()[0]->relevance() - LastResults()[1]->relevance());
  EXPECT_LT(score_diff, 0.01);

  StartSearch(u"title's");
  ASSERT_EQ(LastResults().size(), 2u);
  score_diff =
      abs(LastResults()[0]->relevance() - LastResults()[1]->relevance());
  EXPECT_LT(score_diff, 0.01);
}

TEST_P(GameProviderTest, Policy) {
  SetUpTestingIndex();

  // Results should exist if Suggested Content is enabled.
  profile_->GetPrefs()->SetBoolean(ash::prefs::kSuggestedContentEnabled, true);
  StartSearch(u"first");
  EXPECT_THAT(LastResults(), ElementsAre(Title(u"First Title")));

  // If Suggested Content is disabled, only show results if the override is on.
  profile_->GetPrefs()->SetBoolean(ash::prefs::kSuggestedContentEnabled, false);
  StartSearch(u"first");
  bool enabled_override = GetParam();
  if (enabled_override) {
    EXPECT_THAT(LastResults(), ElementsAre(Title(u"First Title")));
  } else {
    EXPECT_TRUE(LastResults().empty());
  }
}

// Tests that games with the same title but different sources appear in a random
// order across different queries.
TEST_P(GameProviderTest, RandomizeSourceOrder) {
  // Create two games with the same name but different sources.
  GameProvider::GameIndex index;
  index.push_back(MakeAppsResult(u"title", u"source_a"));
  index.push_back(MakeAppsResult(u"title", u"source_b"));
  provider()->SetGameIndexForTest(std::move(index));

  int a_first = 0;
  int b_first = 0;
  for (int i = 0; i < 1000; ++i) {
    StartSearch(u"title");
    ASSERT_EQ(LastResults().size(), 2u);

    // The source name is set into the result details, so use the result details
    // to identify which source it came from.
    if (DetailsEquals(LastResults()[0], u"source_a")) {
      a_first++;
    } else if (DetailsEquals(LastResults()[0], u"source_b")) {
      b_first++;
    }
  }
  ASSERT_EQ(a_first + b_first, 1000);

  // We expect a and b each to be first about ~half the time, but this will vary
  // randomly across test runs. To avoid flakiness, only expect here that they
  // each happen at least 10 times, which has a very high chance of being true.
  EXPECT_GE(a_first, 10);
  EXPECT_GE(b_first, 10);
}

// Tests that threshold is loose enough to include the acronym match results.
TEST_P(GameProviderTest, AcronymMatchIncluded) {
  GameProvider::GameIndex index;
  index.push_back(MakeAppsResult(u"Clash of Clan"));
  index.push_back(MakeAppsResult(u"Assassin's Creed Origins"));
  provider_->SetGameIndexForTest(std::move(index));

  StartSearch(u"coc");
  EXPECT_THAT(LastResults(), ElementsAre(Title(u"Clash of Clan")));

  StartSearch(u"aco");
  EXPECT_THAT(LastResults(), ElementsAre(Title(u"Assassin's Creed Origins")));
}

// Tests that threshold is strict enough to exclude certain problematic cases.
TEST_P(GameProviderTest, ProblematicCasesExcluded) {
  GameProvider::GameIndex index;
  index.push_back(MakeAppsResult(u"Distance"));
  index.push_back(MakeAppsResult(u"Hell Pie"));
  index.push_back(MakeAppsResult(u"Hell Pie Demo"));
  index.push_back(MakeAppsResult(u"Elyon"));
  index.push_back(MakeAppsResult(u"Kill It With Fire"));
  provider_->SetGameIndexForTest(std::move(index));

  StartSearch(u"disney");
  ASSERT_EQ(LastResults().size(), 0u);

  StartSearch(u"help");
  ASSERT_EQ(LastResults().size(), 0u);

  StartSearch(u"layton");
  ASSERT_EQ(LastResults().size(), 0u);

  StartSearch(u"wifi");
  ASSERT_EQ(LastResults().size(), 0u);
}

}  // namespace app_list::test