// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_notifier.h"
#include "ash/public/cpp/test/app_list_test_api.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/webui/help_app_ui/help_app_manager.h"
#include "ash/webui/help_app_ui/help_app_manager_factory.h"
#include "ash/webui/help_app_ui/search/search.mojom.h"
#include "ash/webui/help_app_ui/search/search_handler.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h"
#include "chrome/browser/ash/app_list/search/test/search_results_changed_waiter.h"
#include "chrome/browser/ash/app_list/search/test/test_continue_files_search_provider.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/with_crosapi_param.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/webapps/common/web_app_id.h"
using web_app::test::CrosapiParam;
using web_app::test::WithCrosapiParam;
namespace app_list::test {
class HelpAppSearchBrowserTestBase : public AppListSearchBrowserTest {
public:
HelpAppSearchBrowserTestBase() {
// TODO: Remove parameterization on kProductivityLauncher.
scoped_feature_list_.InitWithFeaturesAndParameters(
{{ash::features::kProductivityLauncher, {{"enable_continue", "true"}}},
{{ash::features::kHelpAppLauncherSearch}, {}}},
{});
}
~HelpAppSearchBrowserTestBase() override = default;
HelpAppSearchBrowserTestBase(const HelpAppSearchBrowserTestBase&) = delete;
HelpAppSearchBrowserTestBase& operator=(const HelpAppSearchBrowserTestBase&) =
delete;
// InProcessBrowserTest:
void SetUpOnMainThread() override {
AppListSearchBrowserTest::SetUpOnMainThread();
AppListClientImpl::GetInstance()->UpdateProfile();
web_app::test::WaitUntilReady(
web_app::WebAppProvider::GetForTest(browser()->profile()));
}
void ShowAppListAndWaitForHelpAppZeroStateResults() {
ShowAppListAndWaitForZeroStateResults(
{ash::AppListSearchResultType::kZeroStateHelpApp});
}
void ShowAppListAndWaitForZeroStateResults(
const std::set<ash::AppListSearchResultType>& result_types) {
SearchResultsChangedWaiter results_waiter(GetClient()->search_controller(),
result_types);
GetClient()->ShowAppList(ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
results_waiter.Wait();
}
// Returns the first published continue section result.
const ChromeSearchResult* FindLeadingContinueSectionResult() {
for (const ChromeSearchResult* result : PublishedResults()) {
if (result->display_type() == ash::SearchResultDisplayType::kContinue)
return result;
}
return nullptr;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class HelpAppSearchBrowserTest : public HelpAppSearchBrowserTestBase {
public:
HelpAppSearchBrowserTest() = default;
// HelpAppSearchBrowserTestBase:
void SetUpOnMainThread() override {
HelpAppSearchBrowserTestBase::SetUpOnMainThread();
AppListClientImpl::GetInstance()->UpdateProfile();
app_list::SearchController* search_controller =
AppListClientImpl::GetInstance()->search_controller();
// For release note chips to show in productivity launcher, continue section
// needs to be visible, which will only be the case if other continue
// results exist - create search provider to inject continue section
// results.
// Adds providers for both local and drive files to test that results
// provided by help app are ranked before both types of files.
auto local_continue_section_provider =
std::make_unique<TestContinueFilesSearchProvider>(
/*for_drive_files=*/false);
local_continue_section_provider_ = local_continue_section_provider.get();
EXPECT_EQ(1u, search_controller->ReplaceProvidersForResultTypeForTest(
local_continue_section_provider_->ResultType(),
std::move(local_continue_section_provider)));
auto drive_continue_section_provider =
std::make_unique<TestContinueFilesSearchProvider>(
/*for_drive_files=*/true);
drive_continue_section_provider_ = drive_continue_section_provider.get();
EXPECT_EQ(1u, search_controller->ReplaceProvidersForResultTypeForTest(
drive_continue_section_provider_->ResultType(),
std::move(drive_continue_section_provider)));
}
raw_ptr<TestContinueFilesSearchProvider, DanglingUntriaged>
local_continue_section_provider_ = nullptr;
raw_ptr<TestContinueFilesSearchProvider, DanglingUntriaged>
drive_continue_section_provider_ = nullptr;
};
// Test that Help App shows up as Release notes if pref shows we have some times
// left to show it.
IN_PROC_BROWSER_TEST_F(HelpAppSearchBrowserTest,
AppListSearchHasReleaseNotesSuggestionChip) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
ShowAppListAndWaitForHelpAppZeroStateResults();
auto* result = FindResult("help-app://updates");
ASSERT_TRUE(result);
EXPECT_EQ(result->title(), l10n_util::GetStringUTF16(
IDS_HELP_APP_WHATS_NEW_CONTINUE_TASK_TITLE));
EXPECT_EQ(result->metrics_type(), ash::HELP_APP_UPDATES);
EXPECT_EQ(result->display_type(), DisplayType::kContinue);
}
// Tests that Help App release notes chip is shown before other continue section
// items.
IN_PROC_BROWSER_TEST_F(HelpAppSearchBrowserTest, ReleaseNoteChipRankedFirst) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
local_continue_section_provider_->set_count(5);
drive_continue_section_provider_->set_count(5);
ShowAppListAndWaitForHelpAppZeroStateResults();
auto* result = FindResult("help-app://updates");
ASSERT_TRUE(result);
EXPECT_EQ(result->display_type(), DisplayType::kContinue);
EXPECT_EQ(FindLeadingContinueSectionResult(), result);
}
// Test that the number of times the suggestion chip should show decreases when
// the chip is shown.
IN_PROC_BROWSER_TEST_F(HelpAppSearchBrowserTest,
ReleaseNotesDecreasesTimesShownOnAppListOpen) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
ash::AppListTestApi app_list_test_api;
app_list_test_api.SetContinueSectionPrivacyNoticeAccepted();
local_continue_section_provider_->set_count(5);
drive_continue_section_provider_->set_count(5);
ShowAppListAndWaitForHelpAppZeroStateResults();
GetClient()->GetNotifier()->FireImpressionTimerForTesting(
ash::AppListNotifier::Location::kContinue);
const int times_left_to_show = GetProfile()->GetPrefs()->GetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
EXPECT_EQ(times_left_to_show, 2);
}
// Test that the number of times the suggestion chip should show decreases when
// the chip is shown in tablet mode.
// https://crbug.com/1489431: test is flaky on bots.
IN_PROC_BROWSER_TEST_F(
HelpAppSearchBrowserTest,
DISABLED_ReleaseNotesDecreasesTimesShownOnAppListOpenInTabletMode) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
ash::AppListTestApi app_list_test_api;
app_list_test_api.SetContinueSectionPrivacyNoticeAccepted();
local_continue_section_provider_->set_count(5);
drive_continue_section_provider_->set_count(5);
SearchResultsChangedWaiter results_waiter(
GetClient()->search_controller(),
{ash::AppListSearchResultType::kZeroStateHelpApp});
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
// Minimize the browser window to show home screen.
browser()->window()->Minimize();
ash::AppListTestApi().WaitForAppListShowAnimation(/*is_bubble_window=*/false);
results_waiter.Wait();
GetClient()->GetNotifier()->FireImpressionTimerForTesting(
ash::AppListNotifier::Location::kContinue);
const int times_left_to_show = GetProfile()->GetPrefs()->GetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
EXPECT_EQ(times_left_to_show, 2);
}
// Test that clicking the Release Notes suggestion chip launches the Help app on
// the What's New page.
IN_PROC_BROWSER_TEST_F(HelpAppSearchBrowserTest,
ClickingReleaseNotesSuggestionChipLaunchesHelpApp) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
ShowAppListAndWaitForHelpAppZeroStateResults();
ChromeSearchResult* result = FindResult("help-app://updates");
// Open the search result. This should open the help app at the expected url.
size_t num_browsers = chrome::GetTotalBrowserCount();
const GURL expected_url("chrome://help-app/updates");
content::TestNavigationObserver navigation_observer(expected_url);
navigation_observer.StartWatchingNewWebContents();
GetClient()->OpenSearchResult(
GetClient()->GetModelUpdaterForTest()->model_id(), result->id(),
/*event_flags=*/0, ash::AppListLaunchedFrom::kLaunchedFromContinueTask,
ash::AppListLaunchType::kAppSearchResult, /*suggestion_index=*/0,
/*launch_as_default=*/false);
navigation_observer.Wait();
EXPECT_EQ(num_browsers + 1, chrome::GetTotalBrowserCount());
EXPECT_EQ(expected_url, chrome::FindLastActive()
->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL());
// Clicking on the chip should stop showing it in the future.
const int times_left_to_show = GetProfile()->GetPrefs()->GetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow);
EXPECT_EQ(times_left_to_show, 0);
}
// Test that the help app provider provides list search results.
IN_PROC_BROWSER_TEST_F(HelpAppSearchBrowserTest,
HelpAppProviderProvidesListResults) {
// Show the app list and simulate search.
AppListClientImpl::GetInstance()->ShowAppList(
ash::AppListShowSource::kSearchKey);
ash::AppListTestApi().WaitForBubbleWindow(
/*wait_for_opening_animation=*/false);
ash::AppListTestApi().SimulateSearch(u"Fix");
// Need this because it sets up the icon.
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
// Add some searchable content to the help app search handler.
std::vector<ash::help_app::mojom::SearchConceptPtr> search_concepts;
auto search_concept = ash::help_app::mojom::SearchConcept::New(
/*id=*/"6318213",
/*title=*/u"Fix connection problems",
/*main_category=*/u"Help",
/*tags=*/std::vector<std::u16string>{u"verycomplicatedsearchquery"},
/*tag_locale=*/"en",
/*url_path_with_parameters=*/"help/id/test",
/*locale=*/"");
search_concepts.push_back(std::move(search_concept));
base::RunLoop run_loop;
ash::help_app::HelpAppManagerFactory::GetForBrowserContext(GetProfile())
->search_handler()
->Update(std::move(search_concepts), base::BindLambdaForTesting([&]() {
run_loop.QuitClosure().Run();
}));
// Wait until the update is complete.
run_loop.Run();
ChromeSearchResult* result = nullptr;
while (!result) {
// Search repeatedly until the desired result is found. Multiple searches
// are needed because it takes time for the icon to load.
SearchAndWaitForProviders("verycomplicatedsearchquery",
{ResultType::kHelpApp});
result = FindResult("chrome://help-app/help/id/test");
}
EXPECT_EQ(base::UTF16ToASCII(result->title()), "Fix connection problems");
EXPECT_EQ(base::UTF16ToASCII(result->details()), "Help");
EXPECT_EQ(result->display_type(), DisplayType::kList);
// Open the search result. This should open the help app at the expected url
// and log a metric indicating what content was launched.
const size_t num_browsers = chrome::GetTotalBrowserCount();
const GURL expected_url("chrome://help-app/help/id/test");
content::TestNavigationObserver navigation_observer(expected_url);
navigation_observer.StartWatchingNewWebContents();
base::HistogramTester histogram_tester;
GetClient()->OpenSearchResult(
GetClient()->GetModelUpdaterForTest()->model_id(), result->id(),
/*event_flags=*/0, ash::AppListLaunchedFrom::kLaunchedFromSearchBox,
ash::AppListLaunchType::kAppSearchResult, /*suggestion_index=*/0,
/*launch_as_default=*/false);
navigation_observer.Wait();
EXPECT_EQ(num_browsers + 1, chrome::GetTotalBrowserCount());
EXPECT_EQ(expected_url, chrome::FindLastActive()
->tab_strip_model()
->GetActiveWebContents()
->GetVisibleURL());
// -20424143 is the hash of the content id. This hash value can be found in
// the enum in the google-internal histogram file.
histogram_tester.ExpectUniqueSample("Discover.LauncherSearch.ContentLaunched",
-20424143, 1);
}
class HelpAppSwaSearchBrowserTest : public HelpAppSearchBrowserTestBase,
public WithCrosapiParam {
public:
HelpAppSwaSearchBrowserTest() = default;
~HelpAppSwaSearchBrowserTest() override = default;
void SetUpOnMainThread() override {
if (browser() == nullptr) {
// Create a new Ash browser window so test code using browser() can work
// even when Lacros is the only browser.
// TODO(crbug.com/40270051): Remove uses of browser() from such tests.
chrome::NewEmptyWindow(ProfileManager::GetActiveUserProfile());
SelectFirstBrowser();
}
HelpAppSearchBrowserTestBase::SetUpOnMainThread();
VerifyLacrosStatus();
}
};
// Test that Help App shows up normally even when suggestion chip should show.
IN_PROC_BROWSER_TEST_P(HelpAppSwaSearchBrowserTest, AppListSearchHasApp) {
ash::SystemWebAppManager::GetForTest(GetProfile())
->InstallSystemAppsForTesting();
GetProfile()->GetPrefs()->SetInteger(
prefs::kReleaseNotesSuggestionChipTimesLeftToShow, 3);
ShowAppListAndWaitForZeroStateResults(
{ash::AppListSearchResultType::kZeroStateHelpApp,
ash::AppListSearchResultType::kZeroStateApp});
auto* result = FindResult(web_app::kHelpAppId);
ASSERT_TRUE(result);
// Has regular app name as title.
EXPECT_EQ(base::UTF16ToASCII(result->title()), "Explore");
}
IN_PROC_BROWSER_TEST_P(HelpAppSwaSearchBrowserTest, Launch) {
Profile* profile = browser()->profile();
ash::SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
const webapps::AppId app_id = web_app::kHelpAppId;
ShowAppListAndWaitForZeroStateResults(
{ash::AppListSearchResultType::kZeroStateHelpApp,
ash::AppListSearchResultType::kZeroStateApp});
auto* result = FindResult(web_app::kHelpAppId);
ASSERT_TRUE(result);
result->Open(ui::EF_NONE);
}
INSTANTIATE_TEST_SUITE_P(All,
HelpAppSwaSearchBrowserTest,
::testing::Values(CrosapiParam::kDisabled,
CrosapiParam::kEnabled),
WithCrosapiParam::ParamToString);
} // namespace app_list::test