// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h"
#import "base/test/task_environment.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/feature_engagement/test/test_tracker.h"
#import "components/omnibox/browser/autocomplete_provider.h"
#import "components/omnibox/browser/fake_autocomplete_provider.h"
#import "components/omnibox/browser/shortcuts_backend.h"
#import "components/omnibox/common/omnibox_features.h"
#import "ios/chrome/browser/autocomplete/model/shortcuts_backend_factory.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/ui/omnibox/test_web_location_bar.h"
#import "ios/chrome/test/block_cleanup_test.h"
#import "ios/testing/nserror_util.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
class ChromeOmniboxClientIOSTest
: public PlatformTest,
public ShortcutsBackend::ShortcutsBackendObserver {
public:
ChromeOmniboxClientIOSTest() = default;
ChromeOmniboxClientIOSTest(const ChromeOmniboxClientIOSTest&) = delete;
ChromeOmniboxClientIOSTest& operator=(const ChromeOmniboxClientIOSTest&) =
delete;
void SetUp() override;
void TearDown() override;
void OnShortcutsLoaded() override;
void OnShortcutsChanged() override;
void InitShortcutsBackend();
bool ShortcutExists(const std::u16string& terms) const;
ShortcutsBackend* shortcuts_backend() { return shortcuts_backend_.get(); }
void UseAutocompleteMatch(const std::u16string& input_text,
const AutocompleteMatch& match);
void FinishCurrentNavigationSuccessfully();
void FailCurrentNavigation();
void RedirectNavigationWithBackButton();
const ShortcutsBackend::ShortcutMap& shortcuts_map() const {
return shortcuts_backend_->shortcuts_map();
}
bool changed_notified() const { return changed_notified_; }
void set_changed_notified(bool changed_notified) {
changed_notified_ = changed_notified;
}
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestWebLocationBar> web_location_bar_;
std::unique_ptr<TestChromeBrowserState> browser_state_;
std::unique_ptr<feature_engagement::Tracker> tracker_;
std::unique_ptr<ChromeOmniboxClientIOS> chrome_omnibox_client_ios_;
std::unique_ptr<web::FakeWebState> web_state_;
std::unique_ptr<web::FakeNavigationContext> navigation_context_;
scoped_refptr<ShortcutsBackend> shortcuts_backend_;
bool load_notified_ = false;
bool changed_notified_ = false;
};
void ChromeOmniboxClientIOSTest::SetUp() {
PlatformTest::SetUp();
load_notified_ = false;
changed_notified_ = false;
TestChromeBrowserState::Builder builder;
builder.AddTestingFactory(ios::ShortcutsBackendFactory::GetInstance(),
ios::ShortcutsBackendFactory::GetDefaultFactory());
browser_state_ = std::move(builder).Build();
web_location_bar_ = std::make_unique<TestWebLocationBar>();
tracker_ = feature_engagement::CreateTestTracker();
chrome_omnibox_client_ios_ = std::make_unique<ChromeOmniboxClientIOS>(
web_location_bar_.get(), browser_state_.get(), tracker_.get());
web_state_ = std::make_unique<web::FakeWebState>();
web_location_bar_->SetWebState(web_state_.get());
navigation_context_ = std::make_unique<web::FakeNavigationContext>();
navigation_context_->SetPageTransition(ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
shortcuts_backend_ =
ios::ShortcutsBackendFactory::GetInstance()->GetForBrowserState(
browser_state_.get());
ASSERT_TRUE(shortcuts_backend_.get());
shortcuts_backend_->AddObserver(this);
}
void ChromeOmniboxClientIOSTest::TearDown() {
PlatformTest::TearDown();
shortcuts_backend_->RemoveObserver(this);
}
void ChromeOmniboxClientIOSTest::InitShortcutsBackend() {
ASSERT_TRUE(shortcuts_backend_);
ASSERT_FALSE(load_notified_);
ASSERT_FALSE(shortcuts_backend_->initialized());
shortcuts_backend_->Init();
task_environment_.RunUntilIdle();
EXPECT_TRUE(load_notified_);
EXPECT_TRUE(shortcuts_backend_->initialized());
}
void ChromeOmniboxClientIOSTest::OnShortcutsLoaded() {
load_notified_ = true;
}
void ChromeOmniboxClientIOSTest::OnShortcutsChanged() {
changed_notified_ = true;
}
bool ChromeOmniboxClientIOSTest::ShortcutExists(
const std::u16string& terms) const {
return shortcuts_map().find(terms) != shortcuts_map().end();
}
void ChromeOmniboxClientIOSTest::UseAutocompleteMatch(
const std::u16string& input_text,
const AutocompleteMatch& match) {
chrome_omnibox_client_ios_->OnAutocompleteAccept(
match.destination_url, match.post_content.get(),
WindowOpenDisposition::CURRENT_TAB, match.transition, match.type,
base::TimeTicks(), false, false, input_text, match, match,
IDNA2008DeviationCharacter::kNone);
}
void ChromeOmniboxClientIOSTest::FinishCurrentNavigationSuccessfully() {
navigation_context_->SetError(nil);
web_state_->OnNavigationFinished(navigation_context_.get());
}
void ChromeOmniboxClientIOSTest::FailCurrentNavigation() {
navigation_context_->SetError(
testing::NSErrorWithLocalizedDescription(@"error"));
web_state_->OnNavigationFinished(navigation_context_.get());
}
void ChromeOmniboxClientIOSTest::RedirectNavigationWithBackButton() {
navigation_context_->SetPageTransition(ui::PAGE_TRANSITION_FORWARD_BACK);
}
// Tests that successful navigations are added to the shortcuts database.
TEST_F(ChromeOmniboxClientIOSTest, SuccessfulNavigationAddsShortcut) {
InitShortcutsBackend();
scoped_refptr<FakeAutocompleteProvider> bookmark_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
AutocompleteMatch bookmark_match(bookmark_provider.get(), 400, true,
AutocompleteMatchType::BOOKMARK_TITLE);
// Navigate to `bookmark_match` with `search_terms`.
std::u16string search_terms = u"input";
UseAutocompleteMatch(search_terms, bookmark_match);
ASSERT_FALSE(changed_notified());
ASSERT_FALSE(ShortcutExists(search_terms));
FinishCurrentNavigationSuccessfully();
// Verify that the successful navigation is added to the database.
ASSERT_TRUE(ShortcutExists(search_terms));
ASSERT_TRUE(changed_notified());
}
// Tests that unfinished navigations or failed navigations are not added in the
// shortcuts database.
TEST_F(ChromeOmniboxClientIOSTest, UnsuccessfulNavigationDontAddShortcut) {
InitShortcutsBackend();
scoped_refptr<FakeAutocompleteProvider> bookmark_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
AutocompleteMatch bookmark_match(bookmark_provider.get(), 400, true,
AutocompleteMatchType::BOOKMARK_TITLE);
// Navigate using `search_terms`.
std::u16string search_terms = u"input";
UseAutocompleteMatch(search_terms, bookmark_match);
// Navigation fails, verify that it's not added to the database.
FailCurrentNavigation();
ASSERT_FALSE(changed_notified());
ASSERT_FALSE(ShortcutExists(search_terms));
// Navigate using `search_terms_2`.
std::u16string search_terms_2 = u"input2";
UseAutocompleteMatch(search_terms_2, bookmark_match);
// Navigate using `search_terms_3`, interrupting the previous navigation.
std::u16string search_terms_3 = u"input3";
UseAutocompleteMatch(search_terms_3, bookmark_match);
ASSERT_FALSE(changed_notified());
ASSERT_FALSE(ShortcutExists(search_terms_2));
ASSERT_FALSE(ShortcutExists(search_terms_3));
FinishCurrentNavigationSuccessfully();
// Verify that the interrupted navigation is not added to the database.
ASSERT_FALSE(ShortcutExists(search_terms_2));
// Verify that the successful navigation is added to the database.
ASSERT_TRUE(ShortcutExists(search_terms_3));
ASSERT_TRUE(changed_notified());
}
// Tests that non omnibox successful navigation are not added in the shortcuts
// database.
TEST_F(ChromeOmniboxClientIOSTest, SuccessfulNonOmniboxDontAddShortcut) {
InitShortcutsBackend();
scoped_refptr<FakeAutocompleteProvider> bookmark_provider =
new FakeAutocompleteProvider(AutocompleteProvider::Type::TYPE_BOOKMARK);
AutocompleteMatch bookmark_match(bookmark_provider.get(), 400, true,
AutocompleteMatchType::BOOKMARK_TITLE);
// Navigate to `bookmark_match` with `search_terms`.
std::u16string search_terms = u"input";
UseAutocompleteMatch(search_terms, bookmark_match);
ASSERT_FALSE(changed_notified());
ASSERT_FALSE(ShortcutExists(search_terms));
// Navigation redirected by pressing the back button.
RedirectNavigationWithBackButton();
// Back button navigation is successful.
FinishCurrentNavigationSuccessfully();
// Verify that the back button navigation is not added to the database.
ASSERT_FALSE(ShortcutExists(search_terms));
ASSERT_FALSE(changed_notified());
}