blob: 2b6bd290037eaa3e1a7a3a6af299d94566920de7 [file] [log] [blame]
// 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 "components/autofill/core/browser/autofill_suggestion_generator.h"
#include <optional>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "components/autofill/core/browser/address_data_manager.h"
#include "components/autofill/core/browser/autofill_granular_filling_utils.h"
#include "components/autofill/core/browser/autofill_suggestion_generator_test_api.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/autofill_wallet_usage_data.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/data_model/credit_card_benefit.h"
#include "components/autofill/core/browser/data_model/iban.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "components/autofill/core/browser/metrics/payments/card_metadata_metrics.h"
#include "components/autofill/core/browser/mock_autofill_optimization_guide.h"
#include "components/autofill/core/browser/payments/constants.h"
#include "components/autofill/core/browser/payments_data_manager.h"
#include "components/autofill/core/browser/personal_data_manager_test_base.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/browser/ui/suggestion_test_helpers.h"
#include "components/autofill/core/browser/ui/suggestion_type.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/grit/components_scaled_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/test/test_sync_service.h"
#include "components/webdata/common/web_data_results.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/mock_resource_bundle_delegate.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/resources/grit/ui_resources.h"
using gfx::test::AreImagesEqual;
namespace autofill {
namespace {
using testing::ElementsAreArray;
using testing::Field;
using testing::IsEmpty;
using testing::Matcher;
using testing::UnorderedElementsAreArray;
constexpr auto kDefaultTriggerSource =
AutofillSuggestionTriggerSource::kFormControlElementClicked;
Matcher<Suggestion> EqualLabels(
const std::vector<std::vector<Suggestion::Text>>& suggestion_objects) {
return Field(&Suggestion::labels, suggestion_objects);
}
Matcher<Suggestion> EqualLabels(
const std::vector<std::vector<std::u16string>>& labels) {
std::vector<std::vector<Suggestion::Text>> suggestion_objects;
for (const auto& row : labels) {
suggestion_objects.emplace_back();
for (const auto& col : row) {
suggestion_objects.back().emplace_back(col);
}
}
return EqualLabels(suggestion_objects);
}
Matcher<Suggestion> EqualsFieldByFieldFillingSuggestion(
SuggestionType id,
const std::u16string& main_text,
FieldType field_by_field_filling_type_used,
const Suggestion::Payload& payload,
const std::vector<std::vector<Suggestion::Text>>& labels = {}) {
return AllOf(
Field(&Suggestion::type, id),
Field(&Suggestion::main_text,
Suggestion::Text(main_text, Suggestion::Text::IsPrimary(true))),
Field(&Suggestion::payload, payload),
Field(&Suggestion::icon, Suggestion::Icon::kNoIcon),
Field(&Suggestion::field_by_field_filling_type_used,
std::optional(field_by_field_filling_type_used)),
EqualLabels(labels));
}
Matcher<Suggestion> EqualsIbanSuggestion(
const std::u16string& text,
const Suggestion::Payload& payload,
const std::u16string& first_label_value) {
return AllOf(Field(&Suggestion::type, SuggestionType::kIbanEntry),
Field(&Suggestion::main_text,
Suggestion::Text(text, Suggestion::Text::IsPrimary(true))),
Field(&Suggestion::payload, payload),
EqualLabels(first_label_value.empty()
? std::vector<std::vector<Suggestion::Text>>{}
: std::vector<std::vector<Suggestion::Text>>{
{Suggestion::Text(first_label_value)}}));
}
#if !BUILDFLAG(IS_IOS)
Matcher<Suggestion> EqualsUndoAutofillSuggestion() {
return EqualsSuggestion(SuggestionType::kClearForm,
#if BUILDFLAG(IS_ANDROID)
base::i18n::ToUpper(l10n_util::GetStringUTF16(
IDS_AUTOFILL_UNDO_MENU_ITEM)),
#else
l10n_util::GetStringUTF16(
IDS_AUTOFILL_UNDO_MENU_ITEM),
#endif
Suggestion::Icon::kUndo);
}
#endif
Matcher<Suggestion> EqualsManageAddressesSuggestion() {
return EqualsSuggestion(
SuggestionType::kAutofillOptions,
l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_ADDRESSES),
Suggestion::Icon::kSettings);
}
Matcher<Suggestion> EqualsManagePaymentsMethodsSuggestion(bool with_gpay_logo) {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
return EqualsSuggestion(
SuggestionType::kAutofillOptions,
l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS),
with_gpay_logo ? Suggestion::Icon::kGooglePay
: Suggestion::Icon::kSettings);
#else
return AllOf(EqualsSuggestion(SuggestionType::kAutofillOptions,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_MANAGE_PAYMENT_METHODS),
Suggestion::Icon::kSettings),
Field(&Suggestion::trailing_icon,
with_gpay_logo ? Suggestion::Icon::kGooglePay
: Suggestion::Icon::kNoIcon));
#endif
}
// Checks that `arg` contains necessary credit card footer suggestions. `arg`
// has to be of type std::vector<Suggestion>.
MATCHER_P(ContainsCreditCardFooterSuggestions, with_gpay_logo, "") {
EXPECT_GT(arg.size(), 2ul);
EXPECT_THAT(arg[arg.size() - 2],
EqualsSuggestion(SuggestionType::kSeparator));
EXPECT_THAT(arg.back(),
EqualsManagePaymentsMethodsSuggestion(with_gpay_logo));
return true;
}
// Checks that `arg` contains necessary address footer suggestions. `arg`
// has to be of type std::vector<Suggestion>.
MATCHER(ContainsAddressFooterSuggestions, "") {
EXPECT_GT(arg.size(), 2ul);
EXPECT_THAT(arg[arg.size() - 2],
EqualsSuggestion(SuggestionType::kSeparator));
EXPECT_THAT(arg.back(), EqualsManageAddressesSuggestion());
return true;
}
} // namespace
// TODO(crbug.com/40176273): Move GetSuggestionsForCreditCard tests and
// BrowserAutofillManagerTestForSharingNickname here from
// browser_autofill_manager_unittest.cc.
class AutofillSuggestionGeneratorTest : public testing::Test {
public:
void SetUp() override {
autofill_client_.SetPrefs(test::PrefServiceForTesting());
personal_data().SetPrefService(autofill_client_.GetPrefs());
personal_data().SetSyncServiceForTest(&sync_service_);
suggestion_generator_ =
std::make_unique<AutofillSuggestionGenerator>(autofill_client_);
autofill_client_.set_autofill_offer_manager(
std::make_unique<AutofillOfferManager>(
&personal_data(),
/*coupon_service_delegate=*/nullptr, /*shopping_service=*/nullptr));
}
void TearDown() override {
if (did_set_up_image_resource_for_test_) {
CleanUpIbanImageResources();
did_set_up_image_resource_for_test_ = false;
}
}
CreditCard CreateServerCard(
const std::string& guid = "00000000-0000-0000-0000-000000000001",
const std::string& server_id = "server_id1",
int instrument_id = 1) {
CreditCard server_card(CreditCard::RecordType::kMaskedServerCard, "a123");
test::SetCreditCardInfo(&server_card, "Elvis Presley", "1111" /* Visa */,
test::NextMonth().c_str(), test::NextYear().c_str(),
"1", /*cvc=*/u"123");
server_card.SetNetworkForMaskedCard(kVisaCard);
server_card.set_server_id(server_id);
server_card.set_guid(guid);
server_card.set_instrument_id(instrument_id);
return server_card;
}
CreditCard CreateLocalCard(
const std::string& guid = "00000000-0000-0000-0000-000000000001") {
CreditCard local_card(guid, test::kEmptyOrigin);
test::SetCreditCardInfo(&local_card, "Elvis Presley", "4111111111111111",
test::NextMonth().c_str(), test::NextYear().c_str(),
"1", /*cvc=*/u"123");
return local_card;
}
gfx::Image CustomIconForTest() { return gfx::test::CreateImage(32, 32); }
void SetUpIbanImageResources() {
original_resource_bundle_ =
ui::ResourceBundle::SwapSharedInstanceForTesting(nullptr);
ui::ResourceBundle::InitSharedInstanceWithLocale(
"en-US", &mock_resource_delegate_,
ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
ON_CALL(mock_resource_delegate_, GetImageNamed(IDR_AUTOFILL_IBAN))
.WillByDefault(testing::Return(CustomIconForTest()));
did_set_up_image_resource_for_test_ = true;
}
void CleanUpIbanImageResources() {
ui::ResourceBundle::CleanupSharedInstance();
ui::ResourceBundle::SwapSharedInstanceForTesting(
original_resource_bundle_.ExtractAsDangling());
}
bool VerifyCardArtImageExpectation(Suggestion& suggestion,
const GURL& expected_url,
const gfx::Image& expected_image) {
#if BUILDFLAG(IS_ANDROID)
return suggestion.custom_icon_url == expected_url;
#else
return AreImagesEqual(suggestion.custom_icon, expected_image);
#endif
}
AutofillSuggestionGenerator& suggestion_generator() {
return *suggestion_generator_.get();
}
TestPersonalDataManager& personal_data() {
return *autofill_client_.GetPersonalDataManager();
}
const std::string& app_locale() { return personal_data().app_locale(); }
TestAutofillClient* autofill_client() { return &autofill_client_; }
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::SYSTEM_TIME};
test::AutofillUnitTestEnvironment autofill_test_environment_;
TestAutofillClient autofill_client_;
syncer::TestSyncService sync_service_;
std::unique_ptr<AutofillSuggestionGenerator> suggestion_generator_;
testing::NiceMock<ui::MockResourceBundleDelegate> mock_resource_delegate_;
raw_ptr<ui::ResourceBundle> original_resource_bundle_;
// Tracks whether SetUpIbanImageResources() has been called, so that the
// created images can be cleaned up when the test has finished.
bool did_set_up_image_resource_for_test_ = false;
};
// Tests that special characters will be used while prefix matching the user's
// field input with the available emails to suggest.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_UseSpecialCharactersInEmail) {
AutofillProfile profile_1(i18n_model_definition::kLegacyHierarchyCountryCode);
AutofillProfile profile_2(i18n_model_definition::kLegacyHierarchyCountryCode);
profile_1.SetRawInfo(EMAIL_ADDRESS, u"test@email.xyz");
profile_2.SetRawInfo(EMAIL_ADDRESS, u"test1@email.xyz");
personal_data().address_data_manager().AddProfile(profile_1);
personal_data().address_data_manager().AddProfile(profile_2);
ASSERT_EQ(
personal_data().address_data_manager().GetProfilesToSuggest().size(), 2u);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>> profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(EMAIL_ADDRESS, u"Test@", false, {});
ASSERT_EQ(profiles.size(), 1u);
EXPECT_EQ(*profiles[0], profile_1);
}
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_HideSubsets) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "Marion", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
// Dupe profile, except different in email address (irrelevant for this form).
AutofillProfile profile1 = profile;
profile1.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
profile1.SetRawInfo(EMAIL_ADDRESS, u"spam_me@example.com");
// Dupe profile, except different in address state.
AutofillProfile profile2 = profile;
profile2.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
profile2.SetRawInfo(ADDRESS_HOME_STATE, u"TX");
// Subset profile.
AutofillProfile profile3 = profile;
profile3.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
profile3.SetRawInfo(ADDRESS_HOME_STATE, std::u16string());
// For easier results verification, make sure |profile| is suggested first.
profile.set_use_count(5);
personal_data().address_data_manager().AddProfile(profile);
personal_data().address_data_manager().AddProfile(profile1);
personal_data().address_data_manager().AddProfile(profile2);
personal_data().address_data_manager().AddProfile(profile3);
// Simulate a form with street address, city and state.
FieldTypeSet types = {ADDRESS_HOME_CITY, ADDRESS_HOME_STATE};
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>> profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(ADDRESS_HOME_STREET_ADDRESS, u"123", false,
types);
ASSERT_EQ(2U, profiles.size());
EXPECT_EQ(profiles[0]->GetRawInfo(ADDRESS_HOME_STATE), u"CA");
EXPECT_EQ(profiles[1]->GetRawInfo(ADDRESS_HOME_STATE), u"TX");
}
// Drawing takes noticeable time when there are more than 10 profiles.
// Therefore, we keep only the 10 first suggested profiles.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_SuggestionsLimit) {
std::vector<AutofillProfile> profiles;
for (size_t i = 0; i < 2 * kMaxDeduplicatedProfilesForSuggestion; i++) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, base::StringPrintf("Marion%zu", i).c_str(),
"Mitchell", "Morrison", "johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
personal_data().address_data_manager().AddProfile(profile);
profiles.push_back(profile);
}
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Ma", false, {});
ASSERT_EQ(2 * kMaxDeduplicatedProfilesForSuggestion,
personal_data().address_data_manager().GetProfiles().size());
ASSERT_EQ(kMaxDeduplicatedProfilesForSuggestion, suggested_profiles.size());
}
// Deduping takes noticeable time when there are more than 50 profiles.
// Therefore, keep only the 50 first pre-dedupe matching profiles.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_ProfilesLimit) {
std::vector<AutofillProfile> profiles;
for (size_t i = 0; i < kMaxPrefixMatchedProfilesForSuggestion; i++) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(
&profile, "Marion", "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox",
base::StringPrintf("%zu123 Zoo St.\nSecond Line\nThird line", i)
.c_str(),
"unit 5", "Hollywood", "CA", "91601", "US", "12345678910");
// Set ranking score such that they appear before the "last" profile (added
// next).
profile.set_use_count(12);
profile.set_use_date(AutofillClock::Now() - base::Days(1));
personal_data().address_data_manager().AddProfile(profile);
profiles.push_back(profile);
}
// Add another profile that matches, but that will get stripped out.
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "Marie", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"000 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile.set_use_count(1);
profile.set_use_date(AutofillClock::Now() - base::Days(7));
personal_data().address_data_manager().AddProfile(profile);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Ma", false, {});
ASSERT_EQ(kMaxPrefixMatchedProfilesForSuggestion + 1,
personal_data().address_data_manager().GetProfiles().size());
ASSERT_EQ(1U, suggested_profiles.size());
EXPECT_EQ(suggested_profiles.front()->GetRawInfo(NAME_FIRST),
profiles.front().GetRawInfo(NAME_FIRST));
}
// Tests that GetProfilesToSuggest orders its suggestions based on the
// ranking formula.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_Ranking) {
// Set up the profiles. They are named with number suffixes X so the X is the
// order in which they should be ordered by the ranking formula.
AutofillProfile profile3(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile3, "Marion3", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile3.set_use_date(AutofillClock::Now() - base::Days(1));
profile3.set_use_count(5);
personal_data().address_data_manager().AddProfile(profile3);
AutofillProfile profile1(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile1, "Marion1", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile1.set_use_date(AutofillClock::Now() - base::Days(1));
profile1.set_use_count(10);
personal_data().address_data_manager().AddProfile(profile1);
AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile2, "Marion2", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile2.set_use_date(AutofillClock::Now() - base::Days(15));
profile2.set_use_count(300);
personal_data().address_data_manager().AddProfile(profile2);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Ma", false, {});
ASSERT_EQ(3U, suggested_profiles.size());
EXPECT_EQ(suggested_profiles[0]->GetRawInfo(NAME_FIRST), u"Marion1");
EXPECT_EQ(suggested_profiles[1]->GetRawInfo(NAME_FIRST), u"Marion2");
EXPECT_EQ(suggested_profiles[2]->GetRawInfo(NAME_FIRST), u"Marion3");
}
// Tests that GetProfilesToSuggest returns all profiles suggestions.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_NumberOfSuggestions) {
// Set up 3 different profiles.
AutofillProfile profile1(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile1, "Marion1", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
personal_data().address_data_manager().AddProfile(profile1);
AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile2, "Marion2", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
personal_data().address_data_manager().AddProfile(profile2);
AutofillProfile profile3(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile3, "Marion3", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
personal_data().address_data_manager().AddProfile(profile3);
// Verify that all the profiles are suggested.
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, std::u16string(), false, {});
EXPECT_EQ(3U, suggested_profiles.size());
}
// Tests that phone number types are correctly deduplicated for suggestions.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_PhoneNumberDeduplication) {
// Set up 2 different profiles.
AutofillProfile profile1(i18n_model_definition::kLegacyHierarchyCountryCode);
profile1.SetRawInfo(NAME_FULL, u"First Middle Last");
profile1.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"+491601234567");
personal_data().address_data_manager().AddProfile(profile1);
AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
profile2.SetRawInfo(NAME_FULL, u"First Middle Last");
profile2.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, u"+491607654321");
personal_data().address_data_manager().AddProfile(profile2);
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FULL, std::u16string(), false,
{NAME_FULL, PHONE_HOME_WHOLE_NUMBER});
EXPECT_EQ(2U, suggested_profiles.size());
}
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FULL, std::u16string(), false,
{NAME_FULL, PHONE_HOME_COUNTRY_CODE,
PHONE_HOME_CITY_AND_NUMBER});
EXPECT_EQ(2U, suggested_profiles.size());
}
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles = test_api(suggestion_generator())
.GetProfilesToSuggest(
NAME_FULL, std::u16string(), false,
{NAME_FULL, PHONE_HOME_COUNTRY_CODE,
PHONE_HOME_CITY_CODE, PHONE_HOME_NUMBER});
EXPECT_EQ(2U, suggested_profiles.size());
}
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(
NAME_FULL, std::u16string(), false,
{NAME_FULL, PHONE_HOME_COUNTRY_CODE, PHONE_HOME_CITY_CODE});
EXPECT_EQ(1U, suggested_profiles.size());
}
}
// Tests that disused profiles are suppressed when suppression is enabled and
// the input field is empty.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_SuppressDisusedProfilesOnEmptyField) {
// Set up 2 different profiles.
AutofillProfile profile1(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile1, "Marion1", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile1.set_use_date(AutofillClock::Now() - base::Days(200));
personal_data().address_data_manager().AddProfile(profile1);
AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile2, "Marion2", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"456 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
profile2.set_use_date(AutofillClock::Now() - base::Days(20));
personal_data().address_data_manager().AddProfile(profile2);
// Query with empty string only returns profile2.
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(ADDRESS_HOME_STREET_ADDRESS,
std::u16string(), false, {});
EXPECT_EQ(1U, suggested_profiles.size());
}
// Query with non-alpha-numeric string only returns profile2.
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(ADDRESS_HOME_STREET_ADDRESS, u"--", false,
{});
EXPECT_EQ(1U, suggested_profiles.size());
}
// Query with prefix for profile1 returns profile1.
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(ADDRESS_HOME_STREET_ADDRESS, u"123",
false, {});
ASSERT_EQ(1U, suggested_profiles.size());
EXPECT_EQ(u"Marion1", suggested_profiles[0]->GetRawInfo(NAME_FIRST));
}
// Query with prefix for profile2 returns profile2.
{
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
suggested_profiles =
test_api(suggestion_generator())
.GetProfilesToSuggest(ADDRESS_HOME_STREET_ADDRESS, u"456",
false, {});
EXPECT_EQ(1U, suggested_profiles.size());
EXPECT_EQ(u"Marion2", suggested_profiles[0]->GetRawInfo(NAME_FIRST));
}
}
// Give two suggestions with the same name, and no other field to compare.
// Expect only one unique suggestion.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_SingleDedupe) {
AutofillProfile profile_1 = test::GetFullProfile();
profile_1.set_use_count(10);
AutofillProfile profile_2 = test::GetFullProfile();
personal_data().address_data_manager().AddProfile(profile_1);
personal_data().address_data_manager().AddProfile(profile_2);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"",
/*field_is_autofilled=*/false, {});
ASSERT_EQ(1U, profiles_to_suggest.size());
}
// Given two suggestions with the same name and one with a different, and also
// last name field to compare, Expect all profiles listed as unique suggestions.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_MultipleDedupe) {
std::vector<AutofillProfile> profiles(
3, AutofillProfile(i18n_model_definition::kLegacyHierarchyCountryCode));
profiles[0].SetRawInfo(NAME_FIRST, u"Bob");
profiles[0].SetRawInfo(NAME_LAST, u"Morrison");
profiles[0].set_use_count(10);
personal_data().address_data_manager().AddProfile(profiles[0]);
profiles[1].SetRawInfo(NAME_FIRST, u"Bob");
profiles[1].SetRawInfo(NAME_LAST, u"Parker");
profiles[1].set_use_count(5);
personal_data().address_data_manager().AddProfile(profiles[1]);
profiles[2].SetRawInfo(NAME_FIRST, u"Mary");
profiles[2].SetRawInfo(NAME_LAST, u"Parker");
personal_data().address_data_manager().AddProfile(profiles[2]);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"",
/*field_is_autofilled=*/false,
{NAME_FIRST, NAME_LAST});
EXPECT_EQ(3U, profiles_to_suggest.size());
}
// Test the limit of number of deduplicated profiles.
TEST_F(AutofillSuggestionGeneratorTest, GetProfilesToSuggest_DedupeLimit) {
std::vector<AutofillProfile> profiles;
for (size_t i = 0; i < kMaxDeduplicatedProfilesForSuggestion + 1; i++) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
profile.SetRawInfo(NAME_FULL,
base::UTF8ToUTF16(base::StringPrintf("Bob %zu Doe", i)));
profile.set_use_count(kMaxDeduplicatedProfilesForSuggestion + 10 - i);
profiles.push_back(profile);
personal_data().address_data_manager().AddProfile(profile);
}
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FULL, u"",
/*field_is_autofilled=*/false, {NAME_FULL});
ASSERT_EQ(kMaxDeduplicatedProfilesForSuggestion, profiles_to_suggest.size());
// All profiles are different.
for (size_t i = 0; i < profiles_to_suggest.size(); i++) {
EXPECT_EQ(profiles_to_suggest[i]->guid(), profiles[i].guid()) << i;
}
}
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_EmptyMatchingProfiles) {
ASSERT_EQ(0U, test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"",
/*field_is_autofilled=*/false, {})
.size());
}
// Tests that `kAccount` profiles are preferred over `kLocalOrSyncable` profile
// in case of a duplicate.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_kAccountPrecedence) {
// Create two profiles that only differ by their source.
AutofillProfile profile_1(i18n_model_definition::kLegacyHierarchyCountryCode);
profile_1.SetRawInfo(NAME_FULL, u"First Last");
profile_1.set_source_for_testing(AutofillProfile::Source::kAccount);
personal_data().address_data_manager().AddProfile(profile_1);
AutofillProfile profile_2(i18n_model_definition::kLegacyHierarchyCountryCode);
profile_2.SetRawInfo(NAME_FULL, u"First Last");
profile_2.set_source_for_testing(AutofillProfile::Source::kLocalOrSyncable);
// Set high use count for profile 2 so that it has greater ranking than
// profile_1
profile_2.set_use_count(100);
personal_data().address_data_manager().AddProfile(profile_2);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FULL, u"",
/*field_is_autofilled=*/false, {NAME_FULL});
ASSERT_EQ(1u, profiles_to_suggest.size());
EXPECT_EQ(profile_1.guid(), profiles_to_suggest[0]->guid());
EXPECT_EQ(AutofillProfile::Source::kAccount,
profiles_to_suggest[0]->source());
}
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_GetMatchingProfile) {
AutofillProfile marion_profile(
i18n_model_definition::kLegacyHierarchyCountryCode);
marion_profile.SetRawInfo(NAME_FIRST, u"Marion");
personal_data().address_data_manager().AddProfile(marion_profile);
AutofillProfile bob_profile(
i18n_model_definition::kLegacyHierarchyCountryCode);
bob_profile.SetRawInfo(NAME_FIRST, u"Bob");
personal_data().address_data_manager().AddProfile(bob_profile);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Mar",
/*field_is_autofilled=*/false, {});
ASSERT_EQ(1U, profiles_to_suggest.size());
EXPECT_EQ(marion_profile.guid(), profiles_to_suggest[0]->guid());
}
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_NoMatchingProfile) {
AutofillProfile bob_profile(
i18n_model_definition::kLegacyHierarchyCountryCode);
bob_profile.SetRawInfo(NAME_FIRST, u"Bob");
personal_data().address_data_manager().AddProfile(bob_profile);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Mar",
/*field_is_autofilled=*/false, {});
ASSERT_TRUE(profiles_to_suggest.empty());
}
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_EmptyProfilesInput) {
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FIRST, u"Mar",
/*field_is_autofilled=*/false, {});
ASSERT_TRUE(profiles_to_suggest.empty());
}
// Tests that disused profiles get removed.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_RemoveProfilesNotUsedSinceTimestamp) {
const char kAddressesSuppressedHistogramName[] =
"Autofill.AddressesSuppressedForDisuse";
base::Time kCurrentTime = AutofillClock::Now();
constexpr size_t kNumProfiles = 10;
constexpr base::TimeDelta k30Days = base::Days(30);
constexpr size_t kNbSuggestions =
(kDisusedDataModelTimeDelta + base::Days(29)) / k30Days;
// Set up the profile vectors with last use dates ranging from `kCurrentTime`
// to 270 days ago, in 30 day increments.
std::vector<AutofillProfile> profiles(
kNumProfiles,
AutofillProfile(i18n_model_definition::kLegacyHierarchyCountryCode));
for (size_t i = 0; i < kNumProfiles; ++i) {
profiles[i].SetRawInfo(
NAME_FULL, base::UTF8ToUTF16(base::StringPrintf("Bob %zu Doe", i)));
profiles[i].set_use_date(kCurrentTime - (i * k30Days));
personal_data().address_data_manager().AddProfile(profiles[i]);
}
// Filter the profiles while capturing histograms.
base::HistogramTester histogram_tester;
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(NAME_FULL, u"",
/*field_is_autofilled=*/false, {NAME_FULL});
// Validate that we get the expected filtered profiles and histograms.
ASSERT_EQ(kNbSuggestions, profiles_to_suggest.size());
for (size_t i = 0; i < kNbSuggestions; ++i) {
EXPECT_EQ(profiles[i].guid(), profiles_to_suggest[i]->guid()) << i;
}
histogram_tester.ExpectTotalCount(kAddressesSuppressedHistogramName, 1);
histogram_tester.ExpectBucketCount(kAddressesSuppressedHistogramName,
kNumProfiles - kNbSuggestions, 1);
}
TEST_F(AutofillSuggestionGeneratorTest, CreateSuggestionsFromProfiles) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "Marion", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile},
{ADDRESS_HOME_STREET_ADDRESS},
/*last_targeted_fields=*/std::nullopt,
ADDRESS_HOME_STREET_ADDRESS,
/*trigger_field_max_length=*/0);
ASSERT_FALSE(suggestions.empty());
EXPECT_EQ(u"123 Zoo St., Second Line, Third line, unit 5",
suggestions[0].main_text.value);
}
TEST_F(AutofillSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_PhoneSubstring) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "Marion", "Mitchell", "Morrison",
"johnwayne@me.xyz", "Fox",
"123 Zoo St.\nSecond Line\nThird line", "unit 5",
"Hollywood", "CA", "91601", "US", "12345678910");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, {PHONE_HOME_WHOLE_NUMBER},
/*last_targeted_fields=*/std::nullopt,
PHONE_HOME_WHOLE_NUMBER,
/*trigger_field_max_length=*/0);
ASSERT_FALSE(suggestions.empty());
EXPECT_EQ(u"+1 234-567-8910", suggestions[0].main_text.value);
}
TEST_F(AutofillSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_PartialNameFieldHasFullNameMainText) {
base::test::ScopedFeatureList features(
features::kAutofillGranularFillingAvailable);
AutofillProfile profile = test::GetFullProfile();
EXPECT_THAT(
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, {NAME_FIRST, NAME_LAST},
/*last_targeted_fields=*/std::nullopt,
NAME_FIRST,
/*trigger_field_max_length=*/0),
SuggestionVectorMainTextsAre(Suggestion::Text(
profile.GetRawInfo(NAME_FULL), Suggestion::Text::IsPrimary(true))));
}
// TODO(crbug.com/40274514): Move AutofillChildrenSuggestionGeneratorTest.
// CreateSuggestionsFromProfiles_GroupFillingLabels_* tests under this fixture.
// Text fixture for label generation related tests. Parameterized by triggering
// field type since how we build labels depends highly on it.
class AutofillLabelSuggestionGeneratorTest
: public AutofillSuggestionGeneratorTest,
public testing::WithParamInterface<FieldType> {
public:
std::u16string GetFullFormFillingLabel(const AutofillProfile& profile) {
// Phone fields are a snow flake, they contain both `NAME_FULL` and
// `ADDRESS_HOME_LINE1`.
const std::u16string label_applied_to_phone_fields =
profile.GetRawInfo(NAME_FULL) + u", " +
profile.GetRawInfo(ADDRESS_HOME_LINE1);
return GetTriggeringFieldType() == ADDRESS_HOME_STREET_ADDRESS
? profile.GetRawInfo(NAME_FULL)
: GetTriggeringFieldType() == PHONE_HOME_WHOLE_NUMBER
? label_applied_to_phone_fields
: profile.GetRawInfo(ADDRESS_HOME_LINE1);
}
FieldType GetTriggeringFieldType() const { return GetParam(); }
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kAutofillGranularFillingAvailable};
};
INSTANTIATE_TEST_SUITE_P(AutofillSuggestionGeneratorTest,
AutofillLabelSuggestionGeneratorTest,
::testing::ValuesIn({NAME_FULL, ADDRESS_HOME_ZIP,
ADDRESS_HOME_STREET_ADDRESS,
PHONE_HOME_WHOLE_NUMBER}));
// Suggestions for `ADDRESS_HOME_LINE1` should have `NAME_FULL` as the label.
// Suggestions for name or address fields which do not include
// `ADDRESS_HOME_LINE1` should have `ADDRESS_HOME_LINE1` as the label.
TEST_P(
AutofillLabelSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_FullFormFilling_SuggestionsHaveCorrectLabels) {
AutofillProfile profile = test::GetFullProfile();
FieldType triggering_field_type = GetTriggeringFieldType();
const std::u16string full_form_filling_label =
GetFullFormFillingLabel(profile);
EXPECT_THAT(
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile},
{NAME_FULL, ADDRESS_HOME_STREET_ADDRESS, ADDRESS_HOME_ZIP},
/*last_targeted_fields=*/std::nullopt, triggering_field_type,
/*trigger_field_max_length=*/0),
ElementsAre(AllOf(EqualLabels({{full_form_filling_label}}))));
}
TEST_P(
AutofillLabelSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_FullFormFilling_SuggestionsNeedMoreLabelsForDifferentiation) {
AutofillProfile profile1 = test::GetFullProfile();
AutofillProfile profile2 = test::GetFullProfile();
profile1.SetRawInfo(EMAIL_ADDRESS, u"hoa@gmail.com");
profile2.SetRawInfo(EMAIL_ADDRESS, u"pham@gmail.com");
// The only difference between the two profiles is the email address.
// That's why the email address is part of the differentiating label.
FieldType triggering_field_type = GetTriggeringFieldType();
const std::u16string full_form_filling_label =
GetFullFormFillingLabel(profile1) +
l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR);
EXPECT_THAT(
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile1, &profile2}, {NAME_FULL, ADDRESS_HOME_STREET_ADDRESS},
/*last_targeted_fields=*/std::nullopt, triggering_field_type,
/*trigger_field_max_length=*/0),
ElementsAre(
AllOf(EqualLabels({{full_form_filling_label + u"hoa@gmail.com"}})),
AllOf(EqualLabels({{full_form_filling_label + u"pham@gmail.com"}}))));
}
// The logic which adds the country as a differentiating label is slightly
// different than the logic which adds any other differentiating label. Since
// the country is the last candidate for a differentiating label, this test also
// prevents random label behaviour (such as non-differentiating label being
// chosen or label not showing at all).
TEST_P(
AutofillLabelSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_FullFormFilling_CountryIsChosenAsDifferentiatingLabel) {
AutofillProfile profile1 = test::GetFullProfile();
AutofillProfile profile2 = profile1;
profile2.SetRawInfo(ADDRESS_HOME_COUNTRY, u"CH");
FieldType triggering_field_type = GetTriggeringFieldType();
const std::u16string full_form_filling_label =
GetFullFormFillingLabel(profile1) +
l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR);
EXPECT_THAT(
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile1, &profile2}, {NAME_FULL, ADDRESS_HOME_STREET_ADDRESS},
/*last_targeted_fields=*/std::nullopt, triggering_field_type,
/*trigger_field_max_length=*/0),
ElementsAre(
AllOf(EqualLabels({{full_form_filling_label + u"United States"}})),
AllOf(EqualLabels({{full_form_filling_label + u"Switzerland"}}))));
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// TODO(crbug.com/325646493): Clean up
// AutofillSuggestionGeneratorTest.AutofillCreditCardBenefitsLabelTest setup and
// parameters.
// Params:
// 1. Function reference to call which creates the appropriate credit card
// benefit for the unittest.
// 2. Issuer ID which is set for the credit card with benefits.
class AutofillCreditCardBenefitsLabelTest
: public AutofillSuggestionGeneratorTest,
public ::testing::WithParamInterface<
std::tuple<base::FunctionRef<CreditCardBenefit()>, std::string>> {
public:
void SetUp() override {
AutofillSuggestionGeneratorTest::SetUp();
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{features::kAutofillEnableCardBenefitsForAmericanExpress,
features::kAutofillEnableCardBenefitsForCapitalOne,
features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName},
/*disabled_features=*/{});
std::u16string benefit_description;
int64_t instrument_id;
if (absl::holds_alternative<CreditCardFlatRateBenefit>(GetBenefit())) {
CreditCardFlatRateBenefit benefit =
absl::get<CreditCardFlatRateBenefit>(GetBenefit());
personal_data().payments_data_manager().AddCreditCardBenefitForTest(
benefit);
benefit_description = benefit.benefit_description();
instrument_id = *benefit.linked_card_instrument_id();
} else if (absl::holds_alternative<CreditCardMerchantBenefit>(
GetBenefit())) {
CreditCardMerchantBenefit benefit =
absl::get<CreditCardMerchantBenefit>(GetBenefit());
personal_data().payments_data_manager().AddCreditCardBenefitForTest(
benefit);
benefit_description = benefit.benefit_description();
instrument_id = *benefit.linked_card_instrument_id();
// Set the page URL in order to ensure that the merchant benefit is
// displayed.
autofill_client()->set_last_committed_primary_main_frame_url(
benefit.merchant_domains().begin()->GetURL());
} else if (absl::holds_alternative<CreditCardCategoryBenefit>(
GetBenefit())) {
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
autofill_client()->GetAutofillOptimizationGuide()),
AttemptToGetEligibleCreditCardBenefitCategory)
.WillByDefault(testing::Return(
CreditCardCategoryBenefit::BenefitCategory::kSubscription));
CreditCardCategoryBenefit benefit =
absl::get<CreditCardCategoryBenefit>(GetBenefit());
personal_data().payments_data_manager().AddCreditCardBenefitForTest(
benefit);
benefit_description = benefit.benefit_description();
instrument_id = *benefit.linked_card_instrument_id();
} else {
NOTREACHED_NORETURN();
}
expected_benefit_text_ = l10n_util::GetStringFUTF16(
IDS_AUTOFILL_CREDIT_CARD_BENEFIT_TEXT_FOR_SUGGESTIONS,
benefit_description);
card_ = CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000001",
/*server_id=*/"server_id1",
/*instrument_id=*/instrument_id);
card_.set_issuer_id(std::get<1>(GetParam()));
personal_data().AddServerCreditCard(card_);
}
CreditCardBenefit GetBenefit() const { return std::get<0>(GetParam())(); }
const CreditCard& card() { return card_; }
const std::u16string& expected_benefit_text() {
return expected_benefit_text_;
}
// Checks that CreateCreditCardSuggestion appropriately labels cards with
// benefits in MetadataLoggingContext.
void DoBenefitSuggestionLabel_MetadataLoggingContextTest() {
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
test_api(suggestion_generator())
.CreateCreditCardSuggestionWithMetadataContext(
card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false, metadata_logging_context);
EXPECT_THAT(metadata_logging_context.instrument_ids_with_benefits_available,
testing::ElementsAre(card().instrument_id()));
}
private:
std::u16string expected_benefit_text_;
CreditCard card_;
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
AutofillSuggestionGeneratorTest,
AutofillCreditCardBenefitsLabelTest,
testing::Combine(testing::Values(&test::GetActiveCreditCardFlatRateBenefit,
&test::GetActiveCreditCardCategoryBenefit,
&test::GetActiveCreditCardMerchantBenefit),
::testing::Values("amex", "capitalone")));
// Checks that for FPAN suggestions that the benefit description is displayed.
TEST_P(AutofillCreditCardBenefitsLabelTest, BenefitSuggestionLabel_Fpan) {
EXPECT_THAT(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false)
.labels,
testing::ElementsAre(
std::vector<Suggestion::Text>{
Suggestion::Text(expected_benefit_text())},
std::vector<Suggestion::Text>{Suggestion::Text(card().GetInfo(
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, /*app_locale=*/"en-US"))}));
}
// Checks that feature_for_iph is set to display the credit card benefit IPH for
// FPAN suggestions with benefits labels.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionFeatureForIph_Fpan) {
EXPECT_EQ(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false)
.feature_for_iph,
&feature_engagement::kIPHAutofillCreditCardBenefitFeature);
}
// Checks that feature_for_iph is set to display the virtual card IPH for
// virtual card suggestions with benefits labels.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionFeatureForIph_VirtualCard) {
EXPECT_EQ(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false)
.feature_for_iph,
&feature_engagement::kIPHAutofillVirtualCardSuggestionFeature);
}
// Checks that for virtual cards suggestion the benefit description is shown
// with a virtual card label appended.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabel_VirtualCard) {
EXPECT_THAT(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false)
.labels,
testing::ElementsAre(
std::vector<Suggestion::Text>{
Suggestion::Text(expected_benefit_text())},
std::vector<Suggestion::Text>{
Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE))}));
}
// Checks that for credit card suggestions with eligible benefits, the
// instrument id of the credit card is marked in the MetadataLoggingContext.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabel_MetadataLoggingContext) {
DoBenefitSuggestionLabel_MetadataLoggingContextTest();
}
// Checks that for credit card suggestions with eligible benefits, the
// instrument id of the credit card is marked in the MetadataLoggingContext. The
// instrument ids should also be available when the benefit flags are disabled.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabel_MetadataLoggingContext_FlagsDisabled) {
base::test::ScopedFeatureList disable_benefits;
disable_benefits.InitWithFeatures(
/*enabled_features=*/{}, /*disabled_features=*/{
features::kAutofillEnableCardBenefitsForAmericanExpress,
features::kAutofillEnableCardBenefitsForCapitalOne});
DoBenefitSuggestionLabel_MetadataLoggingContextTest();
}
// Checks that the merchant benefit description is not displayed for suggestions
// where the webpage's URL is different from the benefit's applicable URL.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabelNotDisplayed_MerchantUrlIsDifferent) {
if (!absl::holds_alternative<CreditCardMerchantBenefit>(GetBenefit())) {
GTEST_SKIP() << "This test should not run for non-merchant benefits.";
}
autofill_client()->set_last_committed_primary_main_frame_url(
GURL("https://random-url.com"));
// Merchant benefit description is not returned.
EXPECT_THAT(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false)
.labels,
testing::ElementsAre(
std::vector<Suggestion::Text>{Suggestion::Text(card().GetInfo(
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, /*app_locale=*/"en-US"))}));
}
// Checks that the category benefit description is not displayed for suggestions
// where the webpage's category in the optimization guide is different from the
// benefit's applicable category.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabelNotDisplayed_CategoryIsDifferent) {
if (!absl::holds_alternative<CreditCardCategoryBenefit>(GetBenefit())) {
GTEST_SKIP() << "This test should not run for non-category benefits.";
}
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
autofill_client()->GetAutofillOptimizationGuide()),
AttemptToGetEligibleCreditCardBenefitCategory)
.WillByDefault(testing::Return(
CreditCardCategoryBenefit::BenefitCategory::kUnknownBenefitCategory));
// Category benefit description is not returned.
EXPECT_THAT(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false)
.labels,
testing::ElementsAre(
std::vector<Suggestion::Text>{Suggestion::Text(card().GetInfo(
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, /*app_locale=*/"en-US"))}));
}
// Checks that the benefit description is not displayed when benefit suggestions
// are disabled for the given card and url.
TEST_P(AutofillCreditCardBenefitsLabelTest,
BenefitSuggestionLabelNotDisplayed_BlockedUrl) {
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
autofill_client()->GetAutofillOptimizationGuide()),
ShouldBlockBenefitSuggestionLabelsForCardAndUrl)
.WillByDefault(testing::Return(true));
// Benefit description is not returned.
EXPECT_THAT(
test_api(suggestion_generator())
.CreateCreditCardSuggestion(card(), CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false)
.labels,
testing::ElementsAre(
std::vector<Suggestion::Text>{Suggestion::Text(card().GetInfo(
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, /*app_locale=*/"en-US"))}));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
class AutofillChildrenSuggestionGeneratorTest
: public AutofillSuggestionGeneratorTest {
public:
std::vector<Suggestion> CreateSuggestionWithChildrenFromProfile(
const AutofillProfile& profile,
std::optional<FieldTypeSet> last_targeted_fields,
FieldType trigger_field_type,
const FieldTypeSet& field_types) {
return test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, field_types,
last_targeted_fields, trigger_field_type,
/*trigger_field_max_length=*/0);
}
std::vector<Suggestion> CreateSuggestionWithChildrenFromProfile(
const AutofillProfile& profile,
std::optional<FieldTypeSet> last_targeted_fields,
FieldType trigger_field_type) {
return CreateSuggestionWithChildrenFromProfile(
profile, last_targeted_fields, trigger_field_type,
{trigger_field_type});
}
std::u16string GetFormattedInternationalNumber() {
return base::UTF8ToUTF16(i18n::FormatPhoneForDisplay(
base::UTF16ToUTF8(
profile().GetInfo(PHONE_HOME_WHOLE_NUMBER, app_locale())),
base::UTF16ToUTF8(profile().GetRawInfo(ADDRESS_HOME_COUNTRY))));
}
std::u16string GetFormattedNationalNumber() {
return base::UTF8ToUTF16(i18n::FormatPhoneNationallyForDisplay(
base::UTF16ToUTF8(
profile().GetInfo(PHONE_HOME_WHOLE_NUMBER, app_locale())),
base::UTF16ToUTF8(profile().GetRawInfo(ADDRESS_HOME_COUNTRY))));
}
const AutofillProfile& profile() const { return profile_; }
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kAutofillGranularFillingAvailable};
// The default profile used to generate suggestions. Uses a country with an
// address model that contains all the field types required by the tests.
const AutofillProfile profile_ =
test::GetFullProfile(AddressCountryCode("AT"));
};
// Test that only "Fill address" is added when the target field is
// `ADDRESS_HOME_LINE1` and no other suggestion exist with the same
// `Suggestion::main_text` and `ADDRESS_HOME_LINE1`.
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddOnlyFillAddress) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/
GetAddressFieldsForGroupFilling(),
/*trigger_field_type=*/ADDRESS_HOME_LINE1,
/*field_types=*/{ADDRESS_HOME_LINE1, ADDRESS_HOME_LINE2});
ASSERT_EQ(suggestions.size(), 1u);
EXPECT_EQ(suggestions[0].labels, std::vector<std::vector<Suggestion::Text>>(
{{Suggestion::Text(u"Fill address")}}));
}
// Test that a differentiating label is added when the `Suggestion::main_text`
// and detailing label are not unique across suggestions.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddFillAddressAndDifferentiatingLabel) {
AutofillProfile profile_1 = test::GetFullProfile();
profile_1.SetRawInfo(NAME_FULL, u"John Doe");
AutofillProfile profile_2 = test::GetFullProfile();
profile_2.SetRawInfo(NAME_FULL, u"John Lemon");
// `profile_1` and `profile_2` have the same `ADDRESS_HOME_LINE1`, which
// will lead to the necessity of a differentiating label (`NAME_FULL`).
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile_1, &profile_2},
{ADDRESS_HOME_LINE1, ADDRESS_HOME_LINE2},
GetAddressFieldsForGroupFilling(), ADDRESS_HOME_LINE1,
/*trigger_field_max_length=*/0);
ASSERT_EQ(suggestions.size(), 2u);
EXPECT_EQ(suggestions[0].labels,
std::vector<std::vector<Suggestion::Text>>(
{{Suggestion::Text(u"Fill address - John Doe")}}));
}
// Test similar to the one above. However also makes sure that
// `ADDRESS_HOME_LINE1` value is added to the label if
// the target field does not contain street address related information
// (ADDRESS_LINE1, ADDRESS_LINE2 and ADDRESS_STREET_NAME).
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddFillAddressAddressLine1AndDifferentiatingLabel) {
AutofillProfile profile_1 = test::GetFullProfile();
profile_1.SetRawInfo(ADDRESS_HOME_CITY, u"Munich");
AutofillProfile profile_2 = test::GetFullProfile();
profile_2.SetRawInfo(ADDRESS_HOME_CITY, u"Frankfurt");
// `profile_1` and `profile_2` have the same `ADDRESS_HOME_ZIP`, which
// will lead to the necessity of a differentiating label
// (`ADDRESS_HOME_CITY`).
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile_1, &profile_2},
{ADDRESS_HOME_LINE1, ADDRESS_HOME_ZIP, ADDRESS_HOME_CITY},
GetAddressFieldsForGroupFilling(), ADDRESS_HOME_ZIP,
/*trigger_field_max_length=*/0);
ASSERT_EQ(suggestions.size(), 2u);
EXPECT_EQ(suggestions[0].labels,
std::vector<std::vector<Suggestion::Text>>({{Suggestion::Text(
u"Fill address - " +
profile_1.GetInfo(ADDRESS_HOME_LINE1, app_locale()) +
u", Munich")}}));
}
// When there is no need to detailing or differentiating label, we add only the
// granular filling label, either "Fill full name" or "Fill address".
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddOnlyFillName) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/
GetFieldTypesOfGroup(FieldTypeGroup::kName),
/*trigger_field_type=*/NAME_FIRST,
/*field_types=*/{NAME_FIRST, NAME_LAST});
ASSERT_EQ(suggestions.size(), 1u);
EXPECT_EQ(suggestions[0].labels,
std::vector<std::vector<Suggestion::Text>>(
{{Suggestion::Text(u"Fill full name")}}));
}
// If the last targeted fields belong to a different group than the triggering
// field, the granular filling label should still match the triggering field.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddOnlyFillName_DifferentLastTargetedFields) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/
GetAddressFieldsForGroupFilling(),
/*trigger_field_type=*/NAME_FIRST,
/*field_types=*/{NAME_FIRST, NAME_LAST});
ASSERT_EQ(suggestions.size(), 1u);
EXPECT_EQ(suggestions[0].labels,
std::vector<std::vector<Suggestion::Text>>(
{{Suggestion::Text(u"Fill full name")}}));
}
// If the last targeted fields belong to a different group than the triggering
// field, the granular filling label should still match the triggering field.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_AddOnlyFillAddress_DifferentLastTargetedFields) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/GetFieldTypesOfGroup(FieldTypeGroup::kName),
/*trigger_field_type=*/ADDRESS_HOME_LINE1,
/*field_types=*/{ADDRESS_HOME_LINE1, ADDRESS_HOME_LINE2});
ASSERT_EQ(suggestions.size(), 1u);
EXPECT_EQ(suggestions[0].labels, std::vector<std::vector<Suggestion::Text>>(
{{Suggestion::Text(u"Fill address")}}));
}
// Test that no labels are added when filling targets only one field.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GroupFillingLabels_SingleFieldFillingHasNoLabels) {
AutofillProfile profile = test::GetFullProfile();
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile}, {NAME_FULL},
GetFieldTypesOfGroup(FieldTypeGroup::kName), NAME_FULL,
/*trigger_field_max_length=*/0);
ASSERT_EQ(suggestions.size(), 1u);
EXPECT_EQ(suggestions[0].labels,
std::vector<std::vector<Suggestion::Text>>({{}}));
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_FirstLevelChildrenSuggestions) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/kAllFieldTypes, NAME_FIRST);
ASSERT_EQ(1U, suggestions.size());
// Test root suggestion
EXPECT_THAT(suggestions,
ElementsAre(Field(
&Suggestion::main_text,
Suggestion::Text(profile().GetInfo(NAME_FULL, app_locale()),
Suggestion::Text::IsPrimary(true)))));
// The children suggestions should be.
//
// 1. fill full name
// 2. first name
// 3. middle name
// 4. family name
// 5. line separator
// 6. company
// 7. address line 1
// 8. address line 2
// 9. City
// 10. Zip
// 11. line separator
// 12. phone number
// 13. email
// 14. line separator
// 15. edit profile
// 16. delete address
ASSERT_EQ(16U, suggestions[0].children.size());
EXPECT_THAT(
suggestions[0].children,
ElementsAre(
EqualsSuggestion(SuggestionType::kFillFullName),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_FIRST, app_locale()), NAME_FIRST,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_MIDDLE, app_locale()), NAME_MIDDLE,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_LAST, app_locale()), NAME_LAST,
Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(COMPANY_NAME, app_locale()), COMPANY_NAME,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_LINE1, app_locale()),
ADDRESS_HOME_LINE1, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_LINE2, app_locale()),
ADDRESS_HOME_LINE2, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_CITY, app_locale()),
ADDRESS_HOME_CITY, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_ZIP, app_locale()),
ADDRESS_HOME_ZIP, Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
// Triggering field is not a phone number, international phone number
// should be shown to the user.
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
GetFormattedInternationalNumber(), PHONE_HOME_WHOLE_NUMBER,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(EMAIL_ADDRESS, app_locale()), EMAIL_ADDRESS,
Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsSuggestion(SuggestionType::kEditAddressProfile),
EqualsSuggestion(SuggestionType::kDeleteAddressProfile)));
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
FillEverythingFromAddressProfile_NotAddedIfNoLastTargetedField) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/std::nullopt, NAME_FIRST);
ASSERT_EQ(1U, suggestions.size());
EXPECT_THAT(suggestions[0].children,
Not(Contains(EqualsSuggestion(
SuggestionType::kFillEverythingFromAddressProfile))))
<< "Children should not contain the 'fill everything' suggestion because "
"there is no `last_targeted_fields`.";
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
IncognitoMode_EditAndDeleteSuggestionsAreNotAdded) {
autofill_client()->set_is_off_the_record(true);
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/std::nullopt, NAME_FIRST);
ASSERT_EQ(1u, suggestions.size());
ASSERT_GT(suggestions[0].children.size(), 0u);
EXPECT_THAT(
suggestions[0].children,
Not(Contains(EqualsSuggestion(SuggestionType::kEditAddressProfile))))
<< "Children should not contain the 'Edit address' suggestion because "
"there user is in incognito mode.";
EXPECT_THAT(
suggestions[0].children,
Not(Contains(EqualsSuggestion(SuggestionType::kDeleteAddressProfile))))
<< "Children should not contain the 'Delete address' suggestion because "
"there user is in incognito mode.";
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
FillEverythingFromAddressProfile_NotAddedIfLastTargetedAllFieldTypes) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/kAllFieldTypes, NAME_FIRST);
ASSERT_EQ(1U, suggestions.size());
EXPECT_THAT(suggestions[0].children,
Not(Contains(EqualsSuggestion(
SuggestionType::kFillEverythingFromAddressProfile))))
<< "Children should not contain the 'fill everything' suggestion because "
"the last targeted fields is `kAllFieldTypes`.";
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
FillEverythingFromAddressProfile_AddedIfFieldByFieldFilling) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/FieldTypeSet{IBAN_VALUE}, NAME_FIRST);
ASSERT_EQ(1U, suggestions.size());
EXPECT_THAT(suggestions[0].children,
Contains(EqualsSuggestion(
SuggestionType::kFillEverythingFromAddressProfile)))
<< "Children should contain the 'fill everything' suggestion because of "
"the last field-by-field filling.";
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_SecondLevelChildrenSuggestions) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(),
/*last_targeted_fields=*/std::nullopt, NAME_FIRST);
ASSERT_EQ(1U, suggestions.size());
// Suggestions should have two levels of children, The address line 1 (sixth
// child) suggestion should have the following children: house number street
// name.
ASSERT_EQ(2U, suggestions[0].children[6].children.size());
EXPECT_THAT(
suggestions[0].children[6].children,
ElementsAre(
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_HOUSE_NUMBER, app_locale()),
ADDRESS_HOME_HOUSE_NUMBER, Suggestion::Guid(profile().guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_HOUSE_NUMBER_SUGGESTION_SECONDARY_TEXT))}}),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_STREET_NAME, app_locale()),
ADDRESS_HOME_STREET_NAME, Suggestion::Guid(profile().guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_STREET_NAME_SUGGESTION_SECONDARY_TEXT))}})));
}
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_LastTargetedFieldsIsSingleField_FieldByFieldFilling) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), std::optional<FieldTypeSet>({NAME_LAST}), NAME_FIRST);
ASSERT_EQ(suggestions.size(), 1u);
// Differently from other filling modes, where when focusing on a name field
// the NAME_FULL is rendered in the main text, field-by-field filling always
// displays the value that will actually be used to fill the field as main
// text.
EXPECT_THAT(suggestions[0],
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_FIRST, app_locale()), NAME_FIRST,
Suggestion::Guid(profile().guid()), {{}}));
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_LastTargetedFieldsIsGroup_GroupFilling) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), std::optional<FieldTypeSet>(GetAddressFieldsForGroupFilling()),
NAME_FIRST, {NAME_FIRST, NAME_LAST});
ASSERT_EQ(1U, suggestions.size());
EXPECT_EQ(suggestions[0].type, SuggestionType::kFillFullName);
EXPECT_EQ(suggestions[0].icon, Suggestion::Icon::kNoIcon);
}
// Note that only full form filling has an icon.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_LastTargetedFieldsAreAllServerFields_FullForm) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, NAME_FIRST, {NAME_FIRST, NAME_LAST});
ASSERT_EQ(1U, suggestions.size());
EXPECT_EQ(suggestions[0].type, SuggestionType::kAddressEntry);
EXPECT_EQ(suggestions[0].icon, Suggestion::Icon::kLocation);
}
// Asserts that when the triggering field is a phone field, the phone number
// suggestion is of type `SuggestionType::kFillFullPhoneNumber`. In other
// scenarios, phone number is of type
// `SuggestionType::kAddressFieldByFieldFilling` as the user expressed intent
// to use their phone number their phone number on a "random" field.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsPhoneField_International) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, PHONE_HOME_WHOLE_NUMBER);
ASSERT_EQ(1U, suggestions.size());
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. company
// 6. address line 1
// 7. address line 2
// 8. City
// 9. Zip
// 10. line separator
// 11. phone number
// 12. email
// 13. line separator
// 14. edit profile
// 15. delete address
ASSERT_EQ(15U, suggestions[0].children.size());
// Triggering field is international phone number type, international phone
// number should be shown to the user.
EXPECT_THAT(suggestions[0].children[10],
EqualsSuggestion(SuggestionType::kFillFullPhoneNumber,
GetFormattedInternationalNumber()));
EXPECT_THAT(suggestions[0].children[10].children, IsEmpty());
}
// Asserts that when the triggering field is a phone field, the phone number
// suggestion is of type `SuggestionType::kFillFullPhoneNumber`. In other
// scenarios, phone number is of type
// `SuggestionType::kAddressFieldByFieldFilling` as the user expressed intent
// to use their phone number on a "random" field.
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsPhoneField_CountryCode) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, PHONE_HOME_COUNTRY_CODE);
ASSERT_EQ(1U, suggestions.size());
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. company
// 6. address line 1
// 7. address line 2
// 8. City
// 9. Zip
// 10. line separator
// 11. phone number
// 12. email
// 13. line separator
// 14. edit profile
// 15. delete address
ASSERT_EQ(15U, suggestions[0].children.size());
// Triggering field is phone number country code, international phone number
// should be shown to the user.
EXPECT_THAT(suggestions[0].children[10],
EqualsSuggestion(SuggestionType::kFillFullPhoneNumber,
GetFormattedInternationalNumber()));
EXPECT_THAT(suggestions[0].children[10].children, IsEmpty());
}
// Asserts that when the triggering field is a phone field, the phone number
// suggestion is of type `SuggestionType::kFillFullPhoneNumber`. In other
// scenarios, phone number is of type
// `SuggestionType::kAddressFieldByFieldFilling` as the user expressed intent
// to use their phone number their phone number on a "random" field.
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsPhoneField_Local) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, PHONE_HOME_CITY_AND_NUMBER);
ASSERT_EQ(1U, suggestions.size());
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. company
// 6. address line 1
// 7. address line 2
// 8. City
// 9. Zip
// 10. line separator
// 11. phone number
// 12. email
// 13. line separator
// 14. edit profile
// 15. delete address
ASSERT_EQ(15U, suggestions[0].children.size());
// Triggering field is local phone number type, local phone number should
// be shown to the user.
EXPECT_THAT(suggestions[0].children[10],
EqualsSuggestion(SuggestionType::kFillFullPhoneNumber,
GetFormattedNationalNumber()));
EXPECT_THAT(suggestions[0].children[10].children, IsEmpty());
}
// Same as above but for email fields.
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsEmailField) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, EMAIL_ADDRESS);
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. company
// 6. address line 1
// 7. address line 2
// 8. City
// 9. Zip
// 10. line separator
// 11. phone number
// 12. email
// 13. line separator
// 14. edit profile
// 15. delete address
ASSERT_EQ(15U, suggestions[0].children.size());
EXPECT_THAT(suggestions[0].children[11],
Field(&Suggestion::type, SuggestionType::kFillFullEmail));
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsAddressField) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, ADDRESS_HOME_LINE1);
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. fill full address
// 6. company
// 7. address line 1
// 8. address line 2
// 9. City
// 10. Zip
// 11. line separator
// 12. phone number
// 13. email
// 14. line separator
// 15. edit address
// 16. delete address
ASSERT_EQ(suggestions.size(), 1u);
ASSERT_EQ(16U, suggestions[0].children.size());
EXPECT_THAT(suggestions[0].children[4],
Field(&Suggestion::type, SuggestionType::kFillFullAddress));
}
TEST_F(AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestionsCompanyField) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), kAllFieldTypes, COMPANY_NAME);
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. fill full address
// 6. company
// 7. address line 1
// 8. address line 2
// 9. City
// 10. Zip
// 11. line separator
// 12. phone number
// 13. email
// 14. line separator
// 15. edit address
// 16. delete address
ASSERT_EQ(suggestions.size(), 1u);
ASSERT_EQ(16U, suggestions[0].children.size());
EXPECT_THAT(suggestions[0].children[4],
Field(&Suggestion::type, SuggestionType::kFillFullAddress));
}
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_ChildrenSuggestions_HouseNumberAndStreetNameCanBeNestedUnderDifferentAddressLines) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
// Update the profile to have house number and street name information in
// different address lines.
profile.SetRawInfo(ADDRESS_HOME_LINE1, u"Amphitheatre Parkway, Brookling");
profile.SetRawInfo(ADDRESS_HOME_LINE2, u"1600 Apartment 1");
profile.SetRawInfo(ADDRESS_HOME_STREET_NAME, u"Amphitheatre Parkway");
profile.SetRawInfo(ADDRESS_HOME_HOUSE_NUMBER, u"1600");
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile, /*last_targeted_fields=*/std::nullopt, ADDRESS_HOME_LINE1);
ASSERT_EQ(1u, suggestions.size());
ASSERT_LE(3u, suggestions[0].children.size());
// The address line 1 (sixth child) should have the street name as child.
EXPECT_THAT(suggestions[0].children[1].children,
ElementsAre(EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile.GetInfo(ADDRESS_HOME_STREET_NAME, app_locale()),
ADDRESS_HOME_STREET_NAME, Suggestion::Guid(profile.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_STREET_NAME_SUGGESTION_SECONDARY_TEXT))}})));
// The address line 2 (seventh child) should have the house number as child.
EXPECT_THAT(
suggestions[0].children[2].children,
ElementsAre(EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile.GetInfo(ADDRESS_HOME_HOUSE_NUMBER, app_locale()),
ADDRESS_HOME_HOUSE_NUMBER, Suggestion::Guid(profile.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_HOUSE_NUMBER_SUGGESTION_SECONDARY_TEXT))}})));
}
TEST_F(
AutofillChildrenSuggestionGeneratorTest,
CreateSuggestionsFromProfiles_GranularityNotFullForm_FillEverythingChildSuggestion) {
// We set only a name field as `last_targeted_fields` to denote that the user
// chose field by field filling.
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), std::optional<FieldTypeSet>({NAME_FIRST}), ADDRESS_HOME_LINE1);
ASSERT_EQ(1U, suggestions.size());
EXPECT_TRUE(base::ranges::any_of(suggestions[0].children, [](auto child) {
return child.type == SuggestionType::kFillEverythingFromAddressProfile;
}));
}
// This fixture contains tests for autofill being triggered from the context
// menu on a field which is not classified as an address.
class AutofillNonAddressFieldsSuggestionGeneratorTest
: public AutofillChildrenSuggestionGeneratorTest {
public:
void SetUp() override {
AutofillChildrenSuggestionGeneratorTest::SetUp();
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kAutofillGranularFillingAvailable,
features::
kAutofillForUnclassifiedFieldsAvailable},
/*disabled_features=*/{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
AllProfilesGenerateSuggestions) {
personal_data().address_data_manager().AddProfile(test::GetFullProfile());
personal_data().address_data_manager().AddProfile(test::GetFullProfile2());
FormFieldData triggering_field;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForProfiles(
{UNKNOWN_TYPE}, triggering_field, UNKNOWN_TYPE,
/*last_targeted_fields=*/std::nullopt,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
EXPECT_EQ(suggestions.size(), 4ul);
EXPECT_THAT(suggestions[0], EqualsSuggestion(SuggestionType::kAddressEntry));
EXPECT_THAT(suggestions[1], EqualsSuggestion(SuggestionType::kAddressEntry));
EXPECT_THAT(suggestions, ContainsAddressFooterSuggestions());
}
// Generally, a profile is displayed with name as main text and address as
// label. But with incomplete profiles, it might be problematic. This test
// creates various incomplete profiles and makes sure that a main text and a
// label are always chosen from the available fields (or only main_text if the
// profile has only one field).
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
SuggestionsAreCorrectAndExpectedLabelsAreCreated) {
std::vector<AutofillProfile> profiles(
5, AutofillProfile(i18n_model_definition::kLegacyHierarchyCountryCode));
profiles[0].SetRawInfo(NAME_FULL, u"John Doe");
profiles[0].SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, u"Address 123");
profiles[1].SetRawInfo(NAME_FULL, u"Johnas Dhonas");
profiles[1].SetRawInfo(ADDRESS_HOME_CITY, u"New York");
profiles[2].SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, u"Other Address 33");
profiles[2].SetRawInfo(ADDRESS_HOME_CITY, u"Old City");
profiles[3].SetRawInfo(ADDRESS_HOME_CITY, u"Munich");
profiles[3].SetRawInfo(EMAIL_ADDRESS, u"munich@gmail.com");
profiles[4].SetRawInfo(EMAIL_ADDRESS, u"other@gmail.com");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profiles[0], &profiles[1], &profiles[2], &profiles[3],
&profiles[4]},
{UNKNOWN_TYPE},
/*last_targeted_fields=*/std::nullopt, UNKNOWN_TYPE,
/*trigger_field_max_length=*/0);
ASSERT_EQ(5u, suggestions.size());
EXPECT_THAT(
suggestions,
ElementsAre(
AllOf(Field(&Suggestion::main_text,
Suggestion::Text(u"John Doe",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"Address 123"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false)),
AllOf(Field(&Suggestion::main_text,
Suggestion::Text(u"Johnas Dhonas",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"New York"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false)),
AllOf(Field(&Suggestion::main_text,
Suggestion::Text(u"Other Address 33",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"Old City"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false)),
AllOf(Field(&Suggestion::main_text,
Suggestion::Text(u"Munich",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"munich@gmail.com"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false)),
AllOf(Field(&Suggestion::main_text,
Suggestion::Text(u"other@gmail.com",
Suggestion::Text::IsPrimary(true))),
EqualLabels(std::vector<std::vector<Suggestion::Text>>{
{Suggestion::Text(u"")}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false))));
}
// This test checks that the resulting string of
// `AutofillProfile::CreateDifferentiatingLabels()` is split correctly into main
// text and labels. In Japanese, the resulting string doesn't have separators.
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
MainTextAndLabelsAreCorrect_Japanese) {
AutofillProfile profile(AddressCountryCode("JP"));
profile.SetRawInfo(NAME_FULL, u"ミク初音");
profile.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, u"港区六本木ヒルズ森タワー");
profile.set_language_code("ja");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, {UNKNOWN_TYPE},
/*last_targeted_fields=*/std::nullopt,
UNKNOWN_TYPE,
/*trigger_field_max_length=*/0);
EXPECT_THAT(suggestions,
ElementsAre(AllOf(
Field(&Suggestion::main_text,
Suggestion::Text(u"港区六本木ヒルズ森タワー",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"ミク初音"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false))));
}
// This test checks that the resulting string of
// `AutofillProfile::CreateDifferentiatingLabels()` is split correctly into main
// text and labels. In Arabic, the resulting string is separated by arabic
// comma.
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
MainTextAndLabelsAreCorrect_Arabic) {
AutofillProfile profile(AddressCountryCode("EG"));
profile.SetRawInfo(NAME_FULL, u"صاحب");
profile.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, u"الملكي");
profile.set_language_code("ar");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, {UNKNOWN_TYPE},
/*last_targeted_fields=*/std::nullopt,
UNKNOWN_TYPE,
/*trigger_field_max_length=*/0);
EXPECT_THAT(
suggestions,
ElementsAre(AllOf(
Field(&Suggestion::main_text,
Suggestion::Text(u"صاحب", Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"الملكي"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false))));
}
// This test checks that the resulting string of
// `AutofillProfile::CreateDifferentiatingLabels()` is split correctly into main
// text and labels. In Thai, the resulting string is separated by space.
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
MainTextAndLabelsAreCorrect_Thai) {
AutofillProfile profile(AddressCountryCode("TH"));
profile.SetRawInfo(NAME_FULL, u"แขวงลุมพินี");
profile.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, u"57 ปาร์คเวนเชอร์");
profile.set_language_code("th");
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles({&profile}, {UNKNOWN_TYPE},
/*last_targeted_fields=*/std::nullopt,
UNKNOWN_TYPE,
/*trigger_field_max_length=*/0);
EXPECT_THAT(suggestions,
ElementsAre(AllOf(
Field(&Suggestion::main_text,
Suggestion::Text(u"แขวงลุมพินี",
Suggestion::Text::IsPrimary(true))),
EqualLabels({{u"57 ปาร์คเวนเชอร์"}}),
Field(&Suggestion::type, SuggestionType::kAddressEntry),
Field(&Suggestion::is_acceptable, false))));
}
// Tests that a non-address field suggestion has all the profile fields as
// children, and doesn't have children like "Fill address" or "Fill full
// name".
TEST_F(AutofillNonAddressFieldsSuggestionGeneratorTest,
SuggestionHasCorrectChildren) {
std::vector<Suggestion> suggestions = CreateSuggestionWithChildrenFromProfile(
profile(), std::nullopt, UNKNOWN_TYPE);
// The child suggestions should be:
//
// 1. first name
// 2. middle name
// 3. family name
// 4. line separator
// 5. company
// 6. address line 1
// 7. address line 2
// 8. City
// 9. Zip
// 10. line separator
// 11. phone number
// 12. email
// 13. line separator
// 14. edit address
// 15. delete address
ASSERT_EQ(suggestions.size(), 1u);
ASSERT_EQ(15u, suggestions[0].children.size());
EXPECT_THAT(
suggestions[0].children,
ElementsAre(
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_FIRST, app_locale()), NAME_FIRST,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_MIDDLE, app_locale()), NAME_MIDDLE,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(NAME_LAST, app_locale()), NAME_LAST,
Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(COMPANY_NAME, app_locale()), COMPANY_NAME,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_LINE1, app_locale()),
ADDRESS_HOME_LINE1, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_LINE2, app_locale()),
ADDRESS_HOME_LINE2, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_CITY, app_locale()),
ADDRESS_HOME_CITY, Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(ADDRESS_HOME_ZIP, app_locale()),
ADDRESS_HOME_ZIP, Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
// Triggering field is not a phone number, international phone number
// should be shown to the user.
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
GetFormattedInternationalNumber(), PHONE_HOME_WHOLE_NUMBER,
Suggestion::Guid(profile().guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kAddressFieldByFieldFilling,
profile().GetInfo(EMAIL_ADDRESS, app_locale()), EMAIL_ADDRESS,
Suggestion::Guid(profile().guid())),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsSuggestion(SuggestionType::kEditAddressProfile),
EqualsSuggestion(SuggestionType::kDeleteAddressProfile)));
}
// Tests the scenario when:
// - autofill is triggered from the context menu on a field which is classified
// as an address field;
// - there is no profile which has values to fill the respective field.
// In this scenario, suggestions should look the same as the ones for an
// unclassified field.
TEST_F(AutofillSuggestionGeneratorTest,
NoProfilesHaveValuesForClassifiedField_AddressManualFallback) {
base::test::ScopedFeatureList features(
features::kAutofillForUnclassifiedFieldsAvailable);
AutofillProfile profile = test::GetIncompleteProfile1();
ASSERT_FALSE(profile.HasRawInfo(PHONE_HOME_WHOLE_NUMBER));
personal_data().address_data_manager().AddProfile(profile);
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForProfiles(
{NAME_FULL, ADDRESS_HOME_STREET_ADDRESS, PHONE_HOME_WHOLE_NUMBER},
FormFieldData(), PHONE_HOME_WHOLE_NUMBER,
/*last_targeted_fields=*/std::nullopt,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
ASSERT_EQ(3u, suggestions.size());
EXPECT_EQ(suggestions[0].type, SuggestionType::kAddressEntry);
// This is the check which actually verifies that the suggestion looks the
// same as the ones for an unclassified field (such a suggestion has
// `is_acceptable` as false).
EXPECT_EQ(suggestions[0].is_acceptable, false);
EXPECT_THAT(suggestions, ContainsAddressFooterSuggestions());
}
// Tests the scenario when:
// - autofill is triggered from the context menu on a field which is classified
// as a credit card field;
// - there is no card which has values to fill the respective field (or the
// field is a CVC which cannot be filled this way).
// In this scenario, suggestions should look the same as the ones for an
// unclassified field.
TEST_F(AutofillSuggestionGeneratorTest,
NoProfilesHaveValuesForClassifiedField_PaymentsManualFallback) {
base::test::ScopedFeatureList features(
features::kAutofillForUnclassifiedFieldsAvailable);
CreditCard card = test::GetIncompleteCreditCard();
ASSERT_FALSE(card.HasRawInfo(PHONE_HOME_WHOLE_NUMBER));
personal_data().payments_data_manager().AddCreditCard(card);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NAME_FULL,
AutofillSuggestionTriggerSource::kManualFallbackPayments,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
ASSERT_EQ(3u, suggestions.size());
EXPECT_EQ(suggestions[0].type, SuggestionType::kCreditCardEntry);
// This is the check which actually verifies that the suggestion looks the
// same as the ones for an unclassified field (such a suggestion has
// `is_acceptable` as false).
EXPECT_EQ(suggestions[0].is_acceptable, false);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
suggestions = suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_VERIFICATION_CODE,
AutofillSuggestionTriggerSource::kManualFallbackPayments,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
ASSERT_EQ(3u, suggestions.size());
EXPECT_EQ(suggestions[0].type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(suggestions[0].is_acceptable, false);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
}
// Tests that regular suggestions are filtered by the triggering field's value,
// but manual fallback suggestions are not.
TEST_F(AutofillSuggestionGeneratorTest, GetSuggestionsForProfiles_Filtering) {
AutofillProfile profile1 = test::GetFullProfile();
AutofillProfile profile2 = test::GetFullProfile2();
personal_data().address_data_manager().AddProfile(profile1);
personal_data().address_data_manager().AddProfile(profile2);
// Create a triggering field those value prefix-matches `profile1`, but not
// `profile2`.
FormFieldData triggering_field;
triggering_field.set_value(profile1.GetRawInfo(NAME_FIRST));
ASSERT_FALSE(profile2.GetRawInfo(NAME_FIRST)
.starts_with(profile1.GetRawInfo(NAME_FIRST)));
// Expect that regular suggestions filter.
std::vector<Suggestion> address_suggestions =
suggestion_generator().GetSuggestionsForProfiles(
{NAME_FIRST}, triggering_field, NAME_FIRST,
/*last_targeted_fields=*/std::nullopt,
AutofillSuggestionTriggerSource::kFormControlElementClicked);
EXPECT_EQ(address_suggestions.size(), 3ul);
EXPECT_THAT(address_suggestions, ContainsAddressFooterSuggestions());
// But manual fallback suggestions do not.
std::vector<Suggestion> manual_fallback_suggestions =
suggestion_generator().GetSuggestionsForProfiles(
{NAME_FIRST}, triggering_field, NAME_FIRST,
/*last_targeted_fields=*/std::nullopt,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
EXPECT_EQ(manual_fallback_suggestions.size(), 4ul);
EXPECT_THAT(manual_fallback_suggestions, ContainsAddressFooterSuggestions());
}
// Tests that regular suggestions are filtered by the last usage timestamp, but
// manual fallback suggestions are not.
TEST_F(AutofillSuggestionGeneratorTest,
GetProfilesToSuggest_TimestampFiltering) {
AutofillProfile profile1 = test::GetFullProfile();
AutofillProfile profile2 = test::GetFullProfile2();
profile2.set_use_date(AutofillClock::Now() - kDisusedDataModelTimeDelta -
base::Days(1));
personal_data().address_data_manager().AddProfile(profile1);
personal_data().address_data_manager().AddProfile(profile2);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest =
test_api(suggestion_generator())
.GetProfilesToSuggest(
NAME_FIRST, /*field_contents=*/u"",
/*field_is_autofilled=*/false, {NAME_FIRST},
AutofillSuggestionTriggerSource::kFormControlElementClicked);
// Expect that left click (or regular triggering) filters profiles.
EXPECT_EQ(profiles_to_suggest.size(), 1u);
std::vector<raw_ptr<const AutofillProfile, VectorExperimental>>
profiles_to_suggest_from_manual_fallback =
test_api(suggestion_generator())
.GetProfilesToSuggest(
NAME_FIRST, /*field_contents=*/u"",
/*field_is_autofilled=*/false, {NAME_FIRST},
AutofillSuggestionTriggerSource::kManualFallbackAddress);
// But manual fallback triggering does not.
EXPECT_EQ(profiles_to_suggest_from_manual_fallback.size(), 2u);
}
#if !BUILDFLAG(IS_IOS)
TEST_F(AutofillSuggestionGeneratorTest, UndoAutofillOnAddressForm) {
personal_data().address_data_manager().AddProfile(test::GetFullProfile());
FormFieldData field;
field.set_is_autofilled(true);
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForProfiles(
{NAME_FIRST}, field, NAME_FIRST,
/*last_targeted_fields=*/std::nullopt, kDefaultTriggerSource);
EXPECT_THAT(suggestions,
ElementsAre(EqualsSuggestion(SuggestionType::kAddressEntry),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsUndoAutofillSuggestion(),
EqualsManageAddressesSuggestion()));
}
#endif
TEST_F(AutofillSuggestionGeneratorTest,
RemoveExpiredCreditCardsNotUsedSinceTimestamp) {
const base::Time kNow = AutofillClock::Now();
const base::Time kDisuseTime =
kNow - kDisusedDataModelTimeDelta - base::Days(1);
size_t card_number = 4111111111111111ul;
std::vector<CreditCard> credit_cards;
for (bool is_local : {false, true}) {
for (bool is_expired : {false, true}) {
for (bool is_disused : {false, true}) {
// Create a credit card based on the current iteration.
CreditCard credit_card =
is_expired ? test::GetExpiredCreditCard() : test::GetCreditCard();
credit_card.SetNumber(base::NumberToString16(card_number++));
credit_card.set_use_date(is_disused ? kDisuseTime : kNow);
if (is_local) {
credit_card.set_record_type(CreditCard::RecordType::kLocalCard);
personal_data().payments_data_manager().AddCreditCard(credit_card);
} else {
credit_card.set_record_type(
CreditCard::RecordType::kMaskedServerCard);
personal_data().AddServerCreditCard(credit_card);
}
credit_cards.push_back(credit_card);
}
}
}
base::HistogramTester histogram_tester;
std::vector<CreditCard> cards_to_suggest =
test_api(suggestion_generator())
.GetOrderedCardsToSuggest(
FormFieldData(), UNKNOWN_TYPE, /*suppress_disused_cards=*/true,
/*prefix_match=*/false, /*include_virtual_cards=*/false);
// Expect that only the last card (disused, expired and local) is removed.
credit_cards.pop_back();
EXPECT_THAT(cards_to_suggest, UnorderedElementsAreArray(credit_cards));
constexpr char kHistogramName[] = "Autofill.CreditCardsSuppressedForDisuse";
histogram_tester.ExpectTotalCount(kHistogramName, 1);
histogram_tester.ExpectBucketCount(kHistogramName, 1, 1);
}
TEST_F(AutofillSuggestionGeneratorTest,
ManualFallback_UnusedExpiredCardsAreNotSuppressed) {
CreditCard local_card = test::GetCreditCard();
local_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, u"04");
local_card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, u"2000");
local_card.set_use_date(AutofillClock::Now() - kDisusedDataModelTimeDelta -
base::Days(1));
personal_data().payments_data_manager().AddCreditCard(local_card);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), UNKNOWN_TYPE,
AutofillSuggestionTriggerSource::kManualFallbackPayments,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_FALSE(suggestions.empty());
}
TEST_F(AutofillSuggestionGeneratorTest, GetServerCardForLocalCard) {
CreditCard server_card = CreateServerCard();
server_card.SetNumber(u"4111111111111111");
personal_data().AddServerCreditCard(server_card);
CreditCard local_card =
CreateLocalCard("00000000-0000-0000-0000-000000000002");
// The server card should be returned if the local card is passed in.
const CreditCard* result =
personal_data().payments_data_manager().GetServerCardForLocalCard(
&local_card);
ASSERT_TRUE(result);
EXPECT_EQ(server_card.guid(), result->guid());
// Should return nullptr if a server card is passed in.
EXPECT_FALSE(
personal_data().payments_data_manager().GetServerCardForLocalCard(
&server_card));
// Should return nullptr if no server card has the same information as the
// local card.
server_card.SetNumber(u"5454545454545454");
personal_data().test_payments_data_manager().ClearCreditCards();
personal_data().AddServerCreditCard(server_card);
EXPECT_FALSE(
personal_data().payments_data_manager().GetServerCardForLocalCard(
&local_card));
}
// The suggestions of credit cards with card linked offers are moved to the
// front. This test checks that the order of the other cards remains stable.
TEST_F(AutofillSuggestionGeneratorTest,
GetSuggestionsForCreditCards_StableSortBasedOnOffer) {
// Create three server cards.
personal_data().test_payments_data_manager().ClearCreditCards();
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000001",
/*server_id=*/"server_id1", /*instrument_id=*/1));
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000002",
/*server_id=*/"server_id2", /*instrument_id=*/2));
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000003",
/*server_id=*/"server_id3", /*instrument_id=*/3));
// Create a card linked offer and attach it to server_card2.
AutofillOfferData offer_data = test::GetCardLinkedOfferData1();
offer_data.SetMerchantOriginForTesting({GURL("http://www.example1.com")});
offer_data.SetEligibleInstrumentIdForTesting({2});
autofill_client()->set_last_committed_primary_main_frame_url(
GURL("http://www.example1.com"));
personal_data().AddAutofillOfferData(offer_data);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_TRUE(with_offer);
ASSERT_EQ(suggestions.size(), 5U);
// The suggestion with card linked offer available should be ranked to the
// top.
EXPECT_EQ(suggestions[0].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000002")));
// The other suggestions should have their relative ranking unchanged.
EXPECT_EQ(suggestions[1].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000003")));
EXPECT_EQ(suggestions[2].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
// Ensures we appropriately generate suggestions for virtual cards on a
// standalone CVC field.
TEST_F(AutofillSuggestionGeneratorTest,
GetSuggestionsForVirtualCardStandaloneCvc) {
CreditCard server_card = CreateServerCard();
personal_data().AddServerCreditCard(server_card);
base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour>
virtual_card_guid_to_last_four_map;
virtual_card_guid_to_last_four_map.insert(
{server_card.guid(), VirtualCardUsageData::VirtualCardLastFour(u"1234")});
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForVirtualCardStandaloneCvc(
FormFieldData(), metadata_logging_context,
virtual_card_guid_to_last_four_map);
ASSERT_EQ(suggestions.size(), 3U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
#if !BUILDFLAG(IS_IOS)
TEST_F(AutofillSuggestionGeneratorTest,
GetSuggestionsForVirtualCardStandaloneCvc_UndoAutofill) {
CreditCard server_card = CreateServerCard();
personal_data().AddServerCreditCard(CreateServerCard());
base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour>
virtual_card_guid_to_last_four_map;
virtual_card_guid_to_last_four_map.insert(
{server_card.guid(), VirtualCardUsageData::VirtualCardLastFour(u"4444")});
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
FormFieldData field;
field.set_is_autofilled(true);
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForVirtualCardStandaloneCvc(
field, metadata_logging_context, virtual_card_guid_to_last_four_map);
EXPECT_THAT(
suggestions,
ElementsAre(
EqualsSuggestion(SuggestionType::kVirtualCreditCardEntry),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsUndoAutofillSuggestion(),
EqualsManagePaymentsMethodsSuggestion(/*with_gpay_logo=*/true)));
}
#endif
// Ensures we appropriately generate suggestions for credit saved with CVC.
TEST_F(AutofillSuggestionGeneratorTest, GetCardSuggestionsWithCvc) {
CreditCard card = test::WithCvc(test::GetMaskedServerCard2());
personal_data().AddServerCreditCard(card);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
ASSERT_EQ(suggestions.size(), 3U);
EXPECT_TRUE(with_cvc);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
// Verifies that the GPay logo is set correctly.
TEST_F(AutofillSuggestionGeneratorTest, ShouldDisplayGpayLogo) {
// GPay logo should be displayed if suggestions were all for server cards;
{
// Create two server cards.
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000001",
/*server_id=*/"server_id1", /*instrument_id=*/1));
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000002",
/*server_id=*/"server_id2", /*instrument_id=*/2));
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_EQ(suggestions.size(), 4U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
personal_data().test_payments_data_manager().ClearCreditCards();
// GPay logo should not be displayed if at least one local card was in the
// suggestions.
{
// Create one server card and one local card.
auto local_card = CreateLocalCard(
/*guid=*/"00000000-0000-0000-0000-000000000001");
local_card.SetNumber(u"5454545454545454");
personal_data().payments_data_manager().AddCreditCard(local_card);
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000002",
/*server_id=*/"server_id2", /*instrument_id=*/2));
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_EQ(suggestions.size(), 4U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
}
personal_data().test_payments_data_manager().ClearCreditCards();
// GPay logo should be displayed if there was an unused expired local card in
// the suggestions.
{
// Create one server card and one unused expired local card.
auto local_card = CreateLocalCard(
/*guid=*/"00000000-0000-0000-0000-000000000001");
local_card.SetNumber(u"5454545454545454");
local_card.SetExpirationYear(2020);
local_card.set_use_date(AutofillClock::Now() - base::Days(365));
personal_data().payments_data_manager().AddCreditCard(local_card);
personal_data().AddServerCreditCard(CreateServerCard(
/*guid=*/"00000000-0000-0000-0000-000000000002",
/*server_id=*/"server_id2", /*instrument_id=*/2));
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_EQ(suggestions.size(), 3U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
}
TEST_F(AutofillSuggestionGeneratorTest, NoSuggestionsWhenNoUserData) {
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
FormFieldData field;
field.set_is_autofilled(true);
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
field, CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/true,
/*should_show_cards_from_account=*/true, with_offer, with_cvc,
metadata_logging_context);
EXPECT_TRUE(suggestions.empty());
}
TEST_F(AutofillSuggestionGeneratorTest, ShouldShowScanCreditCard) {
personal_data().payments_data_manager().AddCreditCard(test::GetCreditCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/true,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_EQ(suggestions.size(), 4ul);
EXPECT_THAT(suggestions[0],
EqualsSuggestion(SuggestionType::kCreditCardEntry));
EXPECT_THAT(
suggestions[1],
EqualsSuggestion(SuggestionType::kScanCreditCard,
l10n_util::GetStringUTF16(IDS_AUTOFILL_SCAN_CREDIT_CARD),
Suggestion::Icon::kScanCreditCard));
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
}
TEST_F(AutofillSuggestionGeneratorTest, ShouldShowCardsFromAccount) {
personal_data().payments_data_manager().AddCreditCard(test::GetCreditCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/true, with_offer, with_cvc,
metadata_logging_context);
EXPECT_EQ(suggestions.size(), 4ul);
EXPECT_THAT(suggestions[0],
EqualsSuggestion(SuggestionType::kCreditCardEntry));
EXPECT_THAT(suggestions[1],
EqualsSuggestion(
SuggestionType::kShowAccountCards,
l10n_util::GetStringUTF16(IDS_AUTOFILL_SHOW_ACCOUNT_CARDS),
Suggestion::Icon::kGoogle));
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
}
#if !BUILDFLAG(IS_IOS)
TEST_F(AutofillSuggestionGeneratorTest,
FieldWasAutofilled_UndoAutofillOnCreditCardForm) {
personal_data().payments_data_manager().AddCreditCard(test::GetCreditCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
FormFieldData field;
field.set_is_autofilled(true);
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
field, CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_THAT(suggestions,
ElementsAre(EqualsSuggestion(SuggestionType::kCreditCardEntry),
EqualsSuggestion(SuggestionType::kSeparator),
EqualsUndoAutofillSuggestion(),
EqualsManagePaymentsMethodsSuggestion(
/*with_gpay_logo=*/false)));
}
#endif
// Test that the virtual card option is shown when all of the prerequisites are
// met.
TEST_F(AutofillSuggestionGeneratorTest, ShouldShowVirtualCardOption) {
// Create a server card.
CreditCard server_card =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
personal_data().AddServerCreditCard(server_card);
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
// If all prerequisites are met, it should return true.
EXPECT_TRUE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&server_card));
EXPECT_TRUE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&local_card));
}
// Test that the virtual card option is shown when the autofill optimization
// guide is not present.
TEST_F(AutofillSuggestionGeneratorTest,
ShouldShowVirtualCardOption_AutofillOptimizationGuideNotPresent) {
// Create a server card.
CreditCard server_card =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
personal_data().AddServerCreditCard(server_card);
autofill_client()->ResetAutofillOptimizationGuide();
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
// If all prerequisites are met, it should return true.
EXPECT_TRUE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&server_card));
EXPECT_TRUE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&local_card));
}
// Test that the virtual card option is shown even if the merchant is opted-out
// of virtual cards.
TEST_F(AutofillSuggestionGeneratorTest,
ShouldShowVirtualCardOption_InDisabledStateForOptedOutMerchants) {
base::test::ScopedFeatureList features(
features::kAutofillEnableVcnGrayOutForMerchantOptOut);
// Create an enrolled server card.
CreditCard server_card =
test::GetMaskedServerCardEnrolledIntoVirtualCardNumber();
personal_data().AddServerCreditCard(server_card);
// Even if the URL is opted-out of virtual cards for `server_card`, display
// the virtual card suggestion.
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
autofill_client()->GetAutofillOptimizationGuide()),
ShouldBlockFormFieldSuggestion)
.WillByDefault(testing::Return(true));
EXPECT_TRUE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&server_card));
}
// Test that the virtual card option is not shown if the merchant is opted-out
// of virtual cards.
TEST_F(AutofillSuggestionGeneratorTest,
ShouldNotShowVirtualCardOption_MerchantOptedOutOfVirtualCards) {
base::test::ScopedFeatureList features;
features.InitAndDisableFeature(
features::kAutofillEnableVcnGrayOutForMerchantOptOut);
// Create an enrolled server card.
CreditCard server_card =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
personal_data().AddServerCreditCard(server_card);
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
// If the URL is opted-out of virtual cards for `server_card`, do not display
// the virtual card suggestion.
auto* optimization_guide = autofill_client()->GetAutofillOptimizationGuide();
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(optimization_guide),
ShouldBlockFormFieldSuggestion)
.WillByDefault(testing::Return(true));
EXPECT_FALSE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&server_card));
EXPECT_FALSE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&local_card));
}
// Test that the virtual card option is not shown if the server card we might be
// showing a virtual card option for is not enrolled into virtual card.
TEST_F(AutofillSuggestionGeneratorTest,
ShouldNotShowVirtualCardOption_ServerCardNotEnrolledInVirtualCard) {
// Create an unenrolled server card.
CreditCard server_card =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kUnspecified);
personal_data().AddServerCreditCard(server_card);
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
// For server card not enrolled, both local and server card should return
// false.
EXPECT_FALSE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&server_card));
EXPECT_FALSE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&local_card));
}
// Test that the virtual card option is not shown for a local card with no
// server card duplicate.
TEST_F(AutofillSuggestionGeneratorTest,
ShouldNotShowVirtualCardOption_LocalCardWithoutServerCardDuplicate) {
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
// The local card does not have a server duplicate, should return false.
EXPECT_FALSE(test_api(suggestion_generator())
.ShouldShowVirtualCardOption(&local_card));
}
TEST_F(AutofillSuggestionGeneratorTest, GetLocalIbanSuggestions) {
SetUpIbanImageResources();
auto MakeLocalIban = [](const std::u16string& value,
const std::u16string& nickname) {
Iban iban(Iban::Guid(base::Uuid::GenerateRandomV4().AsLowercaseString()));
iban.set_value(value);
if (!nickname.empty())
iban.set_nickname(nickname);
return iban;
};
Iban iban0 =
MakeLocalIban(u"CH56 0483 5012 3456 7800 9", u"My doctor's IBAN");
Iban iban1 =
MakeLocalIban(u"DE91 1000 0000 0123 4567 89", u"My brother's IBAN");
Iban iban2 =
MakeLocalIban(u"GR96 0810 0010 0000 0123 4567 890", u"My teacher's IBAN");
Iban iban3 = MakeLocalIban(u"PK70 BANK 0000 1234 5678 9000", u"");
std::vector<Suggestion> iban_suggestions =
AutofillSuggestionGenerator::GetSuggestionsForIbans(
{iban0, iban1, iban2, iban3});
// There are 6 suggestions, 4 for IBAN suggestions, followed by a separator,
// and followed by "Manage payment methods..." which redirects to the Chrome
// payment methods settings page.
ASSERT_EQ(iban_suggestions.size(), 6u);
EXPECT_THAT(
iban_suggestions[0],
EqualsIbanSuggestion(iban0.GetIdentifierStringForAutofillDisplay(),
Suggestion::Guid(iban0.guid()), iban0.nickname()));
EXPECT_THAT(
iban_suggestions[1],
EqualsIbanSuggestion(iban1.GetIdentifierStringForAutofillDisplay(),
Suggestion::Guid(iban1.guid()), iban1.nickname()));
EXPECT_THAT(
iban_suggestions[2],
EqualsIbanSuggestion(iban2.GetIdentifierStringForAutofillDisplay(),
Suggestion::Guid(iban2.guid()), iban2.nickname()));
EXPECT_THAT(
iban_suggestions[3],
EqualsIbanSuggestion(iban3.GetIdentifierStringForAutofillDisplay(),
Suggestion::Guid(iban3.guid()), iban3.nickname()));
EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kSeparator);
EXPECT_EQ(iban_suggestions[5].main_text.value,
l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
EXPECT_EQ(iban_suggestions[5].type, SuggestionType::kAutofillOptions);
}
TEST_F(AutofillSuggestionGeneratorTest, GetServerIbanSuggestions) {
SetUpIbanImageResources();
Iban server_iban1 = test::GetServerIban();
Iban server_iban2 = test::GetServerIban2();
Iban server_iban3 = test::GetServerIban3();
std::vector<Suggestion> iban_suggestions =
AutofillSuggestionGenerator::GetSuggestionsForIbans(
{server_iban1, server_iban2, server_iban3});
// There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
// and followed by "Manage payment methods..." which redirects to the Chrome
// payment methods settings page.
ASSERT_EQ(iban_suggestions.size(), 5u);
EXPECT_THAT(
iban_suggestions[0],
EqualsIbanSuggestion(server_iban1.GetIdentifierStringForAutofillDisplay(),
Suggestion::BackendId(Suggestion::InstrumentId(
server_iban1.instrument_id())),
server_iban1.nickname()));
EXPECT_THAT(
iban_suggestions[1],
EqualsIbanSuggestion(server_iban2.GetIdentifierStringForAutofillDisplay(),
Suggestion::BackendId(Suggestion::InstrumentId(
server_iban2.instrument_id())),
server_iban2.nickname()));
EXPECT_THAT(
iban_suggestions[2],
EqualsIbanSuggestion(server_iban3.GetIdentifierStringForAutofillDisplay(),
Suggestion::BackendId(Suggestion::InstrumentId(
server_iban3.instrument_id())),
server_iban3.nickname()));
EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
EXPECT_EQ(iban_suggestions[4].main_text.value,
l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kAutofillOptions);
}
TEST_F(AutofillSuggestionGeneratorTest, GetLocalAndServerIbanSuggestions) {
SetUpIbanImageResources();
Iban server_iban1 = test::GetServerIban();
Iban server_iban2 = test::GetServerIban2();
Iban local_iban1 = test::GetLocalIban();
std::vector<Suggestion> iban_suggestions =
AutofillSuggestionGenerator::GetSuggestionsForIbans(
{server_iban1, server_iban2, local_iban1});
// There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
// and followed by "Manage payment methods..." which redirects to the Chrome
// payment methods settings page.
ASSERT_EQ(iban_suggestions.size(), 5u);
EXPECT_THAT(
iban_suggestions[0],
EqualsIbanSuggestion(server_iban1.GetIdentifierStringForAutofillDisplay(),
Suggestion::BackendId(Suggestion::InstrumentId(
server_iban1.instrument_id())),
server_iban1.nickname()));
EXPECT_THAT(
iban_suggestions[1],
EqualsIbanSuggestion(server_iban2.GetIdentifierStringForAutofillDisplay(),
Suggestion::BackendId(Suggestion::InstrumentId(
server_iban2.instrument_id())),
server_iban2.nickname()));
EXPECT_THAT(
iban_suggestions[2],
EqualsIbanSuggestion(local_iban1.GetIdentifierStringForAutofillDisplay(),
Suggestion::Guid(local_iban1.guid()),
local_iban1.nickname()));
EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
EXPECT_EQ(iban_suggestions[4].main_text.value,
l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kAutofillOptions);
}
TEST_F(AutofillSuggestionGeneratorTest,
GetPromoCodeSuggestionsFromPromoCodeOffers_ValidPromoCodes) {
std::vector<const AutofillOfferData*> promo_code_offers;
base::Time expiry = AutofillClock::Now() + base::Days(2);
std::vector<GURL> merchant_origins;
DisplayStrings display_strings;
display_strings.value_prop_text = "test_value_prop_text_1";
std::string promo_code = "test_promo_code_1";
AutofillOfferData offer1 = AutofillOfferData::FreeListingCouponOffer(
/*offer_id=*/1, expiry, merchant_origins,
/*offer_details_url=*/GURL("https://offer-details-url.com/"),
display_strings, promo_code);
promo_code_offers.push_back(&offer1);
DisplayStrings display_strings2;
display_strings2.value_prop_text = "test_value_prop_text_2";
std::string promo_code2 = "test_promo_code_2";
AutofillOfferData offer2 = AutofillOfferData::FreeListingCouponOffer(
/*offer_id=*/2, expiry, merchant_origins,
/*offer_details_url=*/GURL("https://offer-details-url.com/"),
display_strings2, promo_code2);
promo_code_offers.push_back(&offer2);
std::vector<Suggestion> promo_code_suggestions =
AutofillSuggestionGenerator::GetPromoCodeSuggestionsFromPromoCodeOffers(
promo_code_offers);
EXPECT_TRUE(promo_code_suggestions.size() == 4);
EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(Suggestion::Guid("1")));
EXPECT_THAT(promo_code_suggestions[0],
EqualLabels({{u"test_value_prop_text_1"}}));
EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(Suggestion::Guid("1")));
EXPECT_EQ(promo_code_suggestions[0].type,
SuggestionType::kMerchantPromoCodeEntry);
EXPECT_EQ(promo_code_suggestions[1].main_text.value, u"test_promo_code_2");
EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(Suggestion::Guid("2")));
EXPECT_THAT(promo_code_suggestions[1],
EqualLabels({{u"test_value_prop_text_2"}}));
EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(Suggestion::Guid("2")));
EXPECT_EQ(promo_code_suggestions[1].type,
SuggestionType::kMerchantPromoCodeEntry);
EXPECT_EQ(promo_code_suggestions[2].type, SuggestionType::kSeparator);
EXPECT_EQ(promo_code_suggestions[3].main_text.value,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_PROMO_CODE_SUGGESTIONS_FOOTER_TEXT));
EXPECT_EQ(promo_code_suggestions[3].GetPayload<GURL>(),
offer1.GetOfferDetailsUrl().spec());
EXPECT_EQ(promo_code_suggestions[3].type,
SuggestionType::kSeePromoCodeDetails);
}
TEST_F(AutofillSuggestionGeneratorTest,
GetPromoCodeSuggestionsFromPromoCodeOffers_InvalidPromoCodeURL) {
std::vector<const AutofillOfferData*> promo_code_offers;
AutofillOfferData offer;
offer.SetPromoCode("test_promo_code_1");
offer.SetValuePropTextInDisplayStrings("test_value_prop_text_1");
offer.SetOfferIdForTesting(1);
offer.SetOfferDetailsUrl(GURL("invalid-url"));
promo_code_offers.push_back(&offer);
std::vector<Suggestion> promo_code_suggestions =
AutofillSuggestionGenerator::GetPromoCodeSuggestionsFromPromoCodeOffers(
promo_code_offers);
EXPECT_TRUE(promo_code_suggestions.size() == 1);
EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
EXPECT_THAT(promo_code_suggestions[0],
EqualLabels({{u"test_value_prop_text_1"}}));
EXPECT_FALSE(
absl::holds_alternative<GURL>(promo_code_suggestions[0].payload));
EXPECT_EQ(promo_code_suggestions[0].type,
SuggestionType::kMerchantPromoCodeEntry);
}
TEST_F(AutofillSuggestionGeneratorTest, TestAddressSuggestion) {
AutofillProfile profile = test::GetFullProfile();
autofill_client()->set_test_addresses({profile});
std::vector<Suggestion> suggestions =
test_api(suggestion_generator())
.CreateSuggestionsFromProfiles(
{&profile}, /*field_types=*/{NAME_FIRST},
/*last_targeted_fields=*/kAllFieldTypes, NAME_FIRST,
/*trigger_field_max_length=*/0);
// There should be test address suggestion and one regular profile
// suggestion.
ASSERT_EQ(suggestions.size(), 2u);
EXPECT_EQ(suggestions[0].type, SuggestionType::kDevtoolsTestAddresses);
EXPECT_EQ(suggestions[1].type, SuggestionType::kAddressEntry);
EXPECT_EQ(suggestions[0].main_text.value, u"Devtools");
EXPECT_THAT(suggestions[0], EqualLabels({{u"Address test data"}}));
EXPECT_EQ(suggestions[0].icon, Suggestion::Icon::kCode);
EXPECT_EQ(suggestions[0].children.size(), 1u);
EXPECT_FALSE(suggestions[0].is_acceptable);
const Suggestion& child = suggestions[0].children.back();
EXPECT_EQ(child.main_text.value, u"United States");
EXPECT_EQ(child.GetBackendId<Suggestion::Guid>().value(), profile.guid());
EXPECT_EQ(child.type, SuggestionType::kDevtoolsTestAddressEntry);
}
// This class helps test the credit card contents that are displayed in
// Autofill suggestions. It covers suggestions on Desktop/Android dropdown,
// and on Android keyboard accessory.
class AutofillCreditCardSuggestionContentTest
: public AutofillSuggestionGeneratorTest {
public:
AutofillCreditCardSuggestionContentTest() {
feature_list_metadata_.InitWithFeatures(
/*enabled_features=*/{features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName,
features::
kAutofillEnableVcnGrayOutForMerchantOptOut},
/*disabled_features=*/{});
}
~AutofillCreditCardSuggestionContentTest() override = default;
bool keyboard_accessory_enabled() const {
#if BUILDFLAG(IS_ANDROID)
return true;
#else
return false;
#endif
}
#if BUILDFLAG(IS_IOS)
// Return the obfuscation length for the last four digits on iOS.
// Although this depends on the kAutofillUseTwoDotsForLastFourDigits flag,
// that flag is not tested explicitly by this test; see
// AutofillCreditCardSuggestionIOSObfuscationLengthContentTest instead.
int ios_obfuscation_length() const {
return base::FeatureList::IsEnabled(
features::kAutofillUseTwoDotsForLastFourDigits)
? 2
: 4;
}
#endif
private:
base::test::ScopedFeatureList feature_list_metadata_;
};
// Verify that the suggestion's texts are populated correctly for a virtual card
// suggestion when the cardholder name field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_VirtualCardMetadata_NameField) {
CreditCard server_card = CreateServerCard();
// Name field suggestion for virtual cards.
Suggestion virtual_card_name_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NAME_FULL,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
if (keyboard_accessory_enabled()) {
// For the keyboard accessory, the "Virtual card" label is added as a prefix
// to the cardholder name.
EXPECT_EQ(virtual_card_name_field_suggestion.main_text.value,
u"Virtual card Elvis Presley");
EXPECT_EQ(virtual_card_name_field_suggestion.minor_text.value, u"");
} else {
// On other platforms, the cardholder name is shown on the first line.
EXPECT_EQ(virtual_card_name_field_suggestion.main_text.value,
u"Elvis Presley");
EXPECT_EQ(virtual_card_name_field_suggestion.minor_text.value, u"");
}
#if BUILDFLAG(IS_IOS)
// There should be 2 lines of labels:
// 1. Obfuscated last 4 digits "..1111" or "....1111".
// 2. Virtual card label.
ASSERT_EQ(virtual_card_name_field_suggestion.labels.size(), 2U);
ASSERT_EQ(virtual_card_name_field_suggestion.labels[0].size(), 1U);
EXPECT_EQ(virtual_card_name_field_suggestion.labels[0][0].value,
CreditCard::GetObfuscatedStringForCardDigits(
ios_obfuscation_length(), u"1111"));
#else
if (keyboard_accessory_enabled()) {
// There should be only 1 line of label: obfuscated last 4 digits "..1111".
EXPECT_THAT(virtual_card_name_field_suggestion,
EqualLabels({{CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/2, u"1111")}}));
} else {
// There should be 2 lines of labels:
// 1. Card name + obfuscated last 4 digits "CardName ....1111". Card name
// and last four are populated separately.
// 2. Virtual card label.
ASSERT_EQ(virtual_card_name_field_suggestion.labels.size(), 2U);
ASSERT_EQ(virtual_card_name_field_suggestion.labels[0].size(), 2U);
EXPECT_EQ(virtual_card_name_field_suggestion.labels[0][0].value, u"Visa");
EXPECT_EQ(virtual_card_name_field_suggestion.labels[0][1].value,
CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/4, u"1111"));
}
#endif
EXPECT_EQ(virtual_card_name_field_suggestion.is_acceptable, true);
if (!keyboard_accessory_enabled()) {
// The virtual card text should be populated in the labels to be shown in a
// new line.
ASSERT_EQ(virtual_card_name_field_suggestion.labels[1].size(), 1U);
EXPECT_EQ(virtual_card_name_field_suggestion.labels[1][0].value,
u"Virtual card");
}
}
// Verify that the suggestion's texts are populated correctly for a virtual card
// suggestion when the card number field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_VirtualCardMetadata_NumberField) {
CreditCard server_card = CreateServerCard();
// Card number field suggestion for virtual cards.
Suggestion virtual_card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
#if BUILDFLAG(IS_IOS)
// Only card number is displayed on the first line.
EXPECT_EQ(
virtual_card_number_field_suggestion.main_text.value,
base::StrCat({u"Visa ", CreditCard::GetObfuscatedStringForCardDigits(
ios_obfuscation_length(), u"1111")}));
EXPECT_EQ(virtual_card_number_field_suggestion.minor_text.value, u"");
#else
if (keyboard_accessory_enabled()) {
// For the keyboard accessory, the "Virtual card" label is added as a prefix
// to the card number. The obfuscated last four digits are shown in a
// separate view.
EXPECT_EQ(virtual_card_number_field_suggestion.main_text.value,
u"Virtual card Visa");
EXPECT_EQ(virtual_card_number_field_suggestion.minor_text.value,
CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/2, u"1111"));
} else {
// Card name and the obfuscated last four digits are shown separately.
EXPECT_EQ(virtual_card_number_field_suggestion.main_text.value, u"Visa");
EXPECT_EQ(virtual_card_number_field_suggestion.minor_text.value,
CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/4, u"1111"));
}
#endif
EXPECT_EQ(virtual_card_number_field_suggestion.is_acceptable, true);
if (keyboard_accessory_enabled()) {
// For the keyboard accessory, there is no label.
ASSERT_TRUE(virtual_card_number_field_suggestion.labels.empty());
} else {
// For Desktop/Android dropdown, and on iOS, "Virtual card" is the label.
EXPECT_THAT(virtual_card_number_field_suggestion,
EqualLabels({{u"Virtual card"}}));
}
}
// Verify that the suggestion's texts are populated correctly for a masked
// server card suggestion when the cardholder name field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_MaskedServerCardMetadata_NameField) {
CreditCard server_card = CreateServerCard();
// Name field suggestion for non-virtual cards.
Suggestion real_card_name_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NAME_FULL,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// Only the name is displayed on the first line.
EXPECT_EQ(real_card_name_field_suggestion.main_text.value, u"Elvis Presley");
EXPECT_EQ(real_card_name_field_suggestion.minor_text.value, u"");
#if BUILDFLAG(IS_IOS)
// For IOS, the label is "..1111" or "....1111".
EXPECT_THAT(real_card_name_field_suggestion,
EqualLabels({{CreditCard::GetObfuscatedStringForCardDigits(
ios_obfuscation_length(), u"1111")}}));
#else
if (keyboard_accessory_enabled()) {
// For the keyboard accessory, the label is "..1111".
EXPECT_THAT(real_card_name_field_suggestion,
EqualLabels({{CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/2, u"1111")}}));
} else {
// For Desktop/Android, the label is "CardName ....1111". Card name and
// last four are shown separately.
ASSERT_EQ(real_card_name_field_suggestion.labels.size(), 1U);
ASSERT_EQ(real_card_name_field_suggestion.labels[0].size(), 2U);
EXPECT_EQ(real_card_name_field_suggestion.labels[0][0].value, u"Visa");
EXPECT_EQ(real_card_name_field_suggestion.labels[0][1].value,
CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/4, u"1111"));
}
#endif
}
// Verify that the suggestion's texts are populated correctly for a masked
// server card suggestion when the card number field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_MaskedServerCardMetadata_NumberField) {
CreditCard server_card = CreateServerCard();
// Card number field suggestion for non-virtual cards.
Suggestion real_card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
#if BUILDFLAG(IS_IOS)
// Only the card number is displayed on the first line.
EXPECT_EQ(
real_card_number_field_suggestion.main_text.value,
base::StrCat({u"Visa ", CreditCard::GetObfuscatedStringForCardDigits(
ios_obfuscation_length(), u"1111")}));
EXPECT_EQ(real_card_number_field_suggestion.minor_text.value, u"");
#else
// For Desktop/Android, split the first line and populate the card name and
// the last 4 digits separately.
EXPECT_EQ(real_card_number_field_suggestion.main_text.value, u"Visa");
EXPECT_EQ(real_card_number_field_suggestion.minor_text.value,
CreditCard::GetObfuscatedStringForCardDigits(
/*obfuscation_length=*/keyboard_accessory_enabled() ? 2 : 4,
u"1111"));
#endif
// The label is the expiration date formatted as mm/yy.
EXPECT_THAT(
real_card_number_field_suggestion,
EqualLabels(
{{base::StrCat({base::UTF8ToUTF16(test::NextMonth()), u"/",
base::UTF8ToUTF16(test::NextYear().substr(2))})}}));
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Verify that the suggestion's texts are populated correctly for a masked
// server card suggestion when payments manual fallback is triggered.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback) {
CreditCard server_card = CreateServerCard();
Suggestion server_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, UNKNOWN_TYPE,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// Only the name is displayed on the first line.
EXPECT_EQ(server_card_suggestion.type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(server_card_suggestion.is_acceptable, false);
// For Desktop, split the first line and populate the card name and
// the last 4 digits separately.
EXPECT_EQ(server_card_suggestion.main_text.value, u"Visa");
EXPECT_EQ(server_card_suggestion.minor_text.value,
server_card.ObfuscatedNumberWithVisibleLastFourDigits(4));
// The label is the expiration date formatted as mm/yy.
EXPECT_THAT(server_card_suggestion,
EqualLabels({{server_card.GetInfo(
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, app_locale())}}));
EXPECT_EQ(server_card_suggestion.acceptance_a11y_announcement,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_A11Y_ANNOUNCE_EXPANDABLE_ONLY_ENTRY));
}
// Verify that the virtual credit card suggestion has the correct
// `Suggestion::type, AX label and is selectable.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_VirtualCreditCard) {
CreditCard enrolled_card = test::GetVirtualCard();
Suggestion enrolled_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(enrolled_card, UNKNOWN_TYPE,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
// Only the name is displayed on the first line.
EXPECT_EQ(enrolled_card_suggestion.type,
SuggestionType::kVirtualCreditCardEntry);
EXPECT_EQ(enrolled_card_suggestion.is_acceptable, true);
EXPECT_EQ(enrolled_card_suggestion.acceptance_a11y_announcement,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_A11Y_ANNOUNCE_VIRTUAL_CARD_MANUAL_FALLBACK_ENTRY));
}
// Verify that the virtual credit card suggestion has the correct labels.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_VirtualCreditCard_Labels) {
CreditCard enrolled_card = test::GetVirtualCard();
Suggestion enrolled_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(enrolled_card, UNKNOWN_TYPE,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
// For Desktop, split the first line and populate the card name and
// the last 4 digits separately.
EXPECT_EQ(enrolled_card_suggestion.main_text.value, u"Mastercard");
EXPECT_EQ(enrolled_card_suggestion.minor_text.value,
enrolled_card.ObfuscatedNumberWithVisibleLastFourDigits(4));
// The label is the expiration date formatted as mm/yy.
EXPECT_EQ(enrolled_card_suggestion.labels.size(), 2U);
EXPECT_EQ(enrolled_card_suggestion.labels[0].size(), 1U);
EXPECT_EQ(
enrolled_card_suggestion.labels[0][0].value,
enrolled_card.GetInfo(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, app_locale()));
EXPECT_EQ(enrolled_card_suggestion.labels[1].size(), 1U);
EXPECT_EQ(enrolled_card_suggestion.labels[1][0].value,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE));
}
// Verify that the virtual credit card suggestion has no nested suggestions.
TEST_F(
AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_VirtualCreditCard_NestedSuggestions) {
CreditCard enrolled_card =
test::GetMaskedServerCardEnrolledIntoVirtualCardNumber();
Suggestion enrolled_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(enrolled_card, UNKNOWN_TYPE,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
EXPECT_TRUE(enrolled_card_suggestion.children.empty());
}
// Verify that the nested suggestion's texts are populated correctly for a
// masked server card suggestion when payments manual fallback is triggered.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_NestedSuggestions) {
CreditCard server_card = test::GetMaskedServerCard();
Suggestion server_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, UNKNOWN_TYPE,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// The child suggestions should be:
//
// 1. Credit card full name
// 2. Credit card number
// 3. Separator
// 4. Credit card expiry date
EXPECT_THAT(
server_card_suggestion.children,
ElementsAre(
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
server_card.GetInfo(CREDIT_CARD_NAME_FULL, app_locale()),
CREDIT_CARD_NAME_FULL, Suggestion::Guid(server_card.guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
server_card.ObfuscatedNumberWithVisibleLastFourDigits(12),
CREDIT_CARD_NUMBER, Suggestion::Guid(server_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_NUMBER_SUGGESTION_LABEL))}}),
AllOf(Field(&Suggestion::type, SuggestionType::kSeparator)),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
server_card.GetInfo(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
app_locale()),
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
Suggestion::Guid(server_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_EXPIRY_DATE_SUGGESTION_LABEL))}})));
}
// Verify that the nested suggestion's texts are populated correctly for a
// credit card with no expiry date set.
TEST_F(
AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_NoExpiryDate_NestedSuggestions) {
CreditCard credit_card;
test::SetCreditCardInfo(&credit_card, /*name_on_card=*/"Cardholder name",
/*card_number=*/"1111222233334444",
/*expiration_month=*/nullptr,
/*expiration_year*/ nullptr,
/*billing_address_id=*/"", /*cvc=*/u"123");
Suggestion server_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(credit_card, UNKNOWN_TYPE,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// The child suggestions should be:
//
// 1. Credit card full name
// 2. Credit card number
EXPECT_THAT(
server_card_suggestion.children,
ElementsAre(
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
credit_card.GetInfo(CREDIT_CARD_NAME_FULL, app_locale()),
CREDIT_CARD_NAME_FULL, Suggestion::Guid(credit_card.guid())),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
credit_card.ObfuscatedNumberWithVisibleLastFourDigits(12),
CREDIT_CARD_NUMBER, Suggestion::Guid(credit_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_NUMBER_SUGGESTION_LABEL))}})));
}
// Verify that the nested suggestion's texts are populated correctly for a
// credit card with no cardholder name and credit card number.
TEST_F(
AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_NoNameAndNumber_NestedSuggestions) {
CreditCard credit_card;
test::SetCreditCardInfo(&credit_card, /*name_on_card=*/nullptr,
/*card_number=*/nullptr, test::NextMonth().c_str(),
test::NextYear().c_str(),
/*billing_address_id=*/"", /*cvc=*/u"123");
Suggestion server_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(credit_card, UNKNOWN_TYPE,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// The child suggestions should be:
//
// 1. Credit card expiry date
EXPECT_THAT(
server_card_suggestion.children,
ElementsAre(EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
credit_card.GetInfo(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, app_locale()),
CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,
Suggestion::Guid(credit_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_EXPIRY_DATE_SUGGESTION_LABEL))}})));
}
// Verify nested suggestions of the expiry date suggestion.
TEST_F(AutofillCreditCardSuggestionContentTest,
CreateCreditCardSuggestion_ManualFallback_NestedExpiryDateSuggestions) {
CreditCard server_card = CreateServerCard();
Suggestion server_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, UNKNOWN_TYPE,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// The expiry date child suggestions should be:
//
// 1. Expiry year.
// 2. Expiry month.
EXPECT_THAT(
server_card_suggestion.children[3].children,
ElementsAre(
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
server_card.GetInfo(CREDIT_CARD_EXP_MONTH, app_locale()),
CREDIT_CARD_EXP_MONTH, Suggestion::Guid(server_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_EXPIRY_MONTH_SUGGESTION_LABEL))}}),
EqualsFieldByFieldFillingSuggestion(
SuggestionType::kCreditCardFieldByFieldFilling,
server_card.GetInfo(CREDIT_CARD_EXP_2_DIGIT_YEAR, app_locale()),
CREDIT_CARD_EXP_2_DIGIT_YEAR,
Suggestion::Guid(server_card.guid()),
{{Suggestion::Text(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PAYMENTS_MANUAL_FALLBACK_AUTOFILL_POPUP_CC_EXPIRY_YEAR_SUGGESTION_LABEL))}})));
}
// Verify that manual fallback credit card suggestions are not filtered.
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_ManualFallbackSuggestionsNotFiltered) {
personal_data().AddServerCreditCard(CreateServerCard());
FormFieldData field_data;
field_data.set_value(u"$$$");
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
field_data, UNKNOWN_TYPE,
AutofillSuggestionTriggerSource::kManualFallbackPayments,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
// Credit card suggestions should not depend on the field's value.
EXPECT_EQ(suggestions.size(), 3U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// Verify that the suggestion's texts are populated correctly for a local and
// server card suggestion when the CVC field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_CvcField) {
// Create one server card and one local card with CVC.
CreditCard local_card = CreateLocalCard();
// We used last 4 to deduplicate local card and server card so we should set
// local card with different last 4.
local_card.SetNumber(u"5454545454545454");
personal_data().payments_data_manager().AddCreditCard(std::move(local_card));
personal_data().AddServerCreditCard(CreateServerCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
const std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_VERIFICATION_CODE, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
// Both local card and server card suggestion should be shown when CVC field
// is focused.
ASSERT_EQ(suggestions.size(), 4U);
#if !BUILDFLAG(IS_ANDROID)
EXPECT_EQ(suggestions[0].main_text.value, u"CVC");
EXPECT_EQ(suggestions[1].main_text.value, u"CVC");
EXPECT_EQ(suggestions[0].minor_text.value, u"");
EXPECT_EQ(suggestions[1].minor_text.value, u"");
#else
EXPECT_EQ(suggestions[0].main_text.value, u"CVC for Visa");
EXPECT_EQ(suggestions[1].main_text.value, u"CVC for Mastercard");
EXPECT_EQ(suggestions[0].minor_text.value, u"");
EXPECT_EQ(suggestions[1].minor_text.value, u"");
#endif
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/false));
}
// Verify that the suggestion's texts are populated correctly for a duplicate
// local and server card suggestion when the CVC field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_Duplicate_CvcField) {
// Create 2 duplicate local and server card with same last 4.
personal_data().payments_data_manager().AddCreditCard(CreateLocalCard());
personal_data().AddServerCreditCard(CreateServerCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
const std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_VERIFICATION_CODE, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
// Only 1 suggestion + footer should be shown when CVC field is focused.
ASSERT_EQ(suggestions.size(), 3U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
// Verify that the FPAN and VCN suggestion's texts are populated correctly for a
// enrolled card when the CVC field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_VirtualCard_CvcField) {
// Create a server card with CVC that enrolled to virtual card.
CreditCard server_card = CreateServerCard();
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
personal_data().AddServerCreditCard(std::move(server_card));
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
const std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_VERIFICATION_CODE, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
// Both FPAN and VCN suggestion should be shown when CVC field is focused.
ASSERT_EQ(suggestions.size(), 4U);
#if !BUILDFLAG(IS_ANDROID)
EXPECT_EQ(suggestions[0].main_text.value, u"CVC");
EXPECT_EQ(suggestions[1].main_text.value, u"CVC");
EXPECT_EQ(suggestions[0].minor_text.value, u"");
EXPECT_EQ(suggestions[1].minor_text.value, u"");
#else
EXPECT_EQ(suggestions[0].main_text.value, u"Virtual card CVC for Visa");
EXPECT_EQ(suggestions[1].main_text.value, u"CVC for Visa");
EXPECT_EQ(suggestions[0].minor_text.value, u"");
EXPECT_EQ(suggestions[1].minor_text.value, u"");
#endif
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
// Verify that the FPAN and VCN suggestion's texts are populated correctly for a
// enrolled card when the CVC field is focused.
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_VirtualCard_Duplicate_CvcField) {
// Create duplicate local and server card with CVC that enrolled to virtual
// card.
CreditCard server_card = CreateServerCard();
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
personal_data().AddServerCreditCard(std::move(server_card));
personal_data().payments_data_manager().AddCreditCard(CreateLocalCard());
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
const std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_VERIFICATION_CODE, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
// Both FPAN and VCN suggestion should be shown when CVC field is focused.
ASSERT_EQ(suggestions.size(), 4U);
EXPECT_THAT(suggestions,
ContainsCreditCardFooterSuggestions(/*with_gpay_logo=*/true));
}
#if BUILDFLAG(IS_IOS)
TEST_F(AutofillCreditCardSuggestionContentTest,
GetSuggestionsForCreditCards_LargeKeyboardAccessoryFormat) {
// Enable formatting for large keyboard accessories.
autofill_client()->set_format_for_large_keyboard_accessory(true);
CreditCard server_card = CreateServerCard();
int obfuscation_length = ios_obfuscation_length();
const std::u16string obfuscated_number =
CreditCard::GetObfuscatedStringForCardDigits(obfuscation_length, u"1111");
const std::u16string name_full =
server_card.GetRawInfo(CREDIT_CARD_NAME_FULL);
const std::u16string exp_date =
server_card.GetRawInfo(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR);
const std::u16string card_type = server_card.GetRawInfo(CREDIT_CARD_TYPE);
const std::u16string type_and_number =
base::StrCat({card_type, u" ", obfuscated_number});
Suggestion card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// From the credit card number field, the suggestion should show the card type
// and number and the label should show the expiration date.
EXPECT_EQ(card_number_field_suggestion.main_text.value, type_and_number);
EXPECT_THAT(card_number_field_suggestion, EqualLabels({{exp_date}}));
card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NAME_FULL,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// From the credit card name field, the suggestion should show the full name
// and the label should show the card type and number.
EXPECT_EQ(card_number_field_suggestion.main_text.value,
base::StrCat({name_full}));
EXPECT_THAT(card_number_field_suggestion, EqualLabels({{type_and_number}}));
card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_EXP_MONTH,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// From a credit card expiry field, the suggestion should show the expiration
// date and the label should show the card type and number.
EXPECT_EQ(card_number_field_suggestion.main_text.value,
base::StrCat({exp_date}));
EXPECT_THAT(card_number_field_suggestion, EqualLabels({{type_and_number}}));
server_card.set_record_type(CreditCard::RecordType::kVirtualCard);
card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
// From a virtual credit card, the suggestion should show the card name and
// the label should show the card's virtual status, type and number.
EXPECT_EQ(card_number_field_suggestion.main_text.value,
base::StrCat({server_card.CardNameForAutofillDisplay(
server_card.nickname())}));
EXPECT_THAT(
card_number_field_suggestion,
EqualLabels({{l10n_util::GetStringUTF16(
IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE) +
u" • " + card_type + u" " + obfuscated_number}}));
}
// Tests that credit card suggestions on iOS use the correct number of '•'
// characters depending on the kAutofillUseTwoDotsForLastFourDigits feature.
class AutofillCreditCardSuggestionIOSObfuscationLengthContentTest
: public AutofillSuggestionGeneratorTest,
public testing::WithParamInterface<bool> {
public:
AutofillCreditCardSuggestionIOSObfuscationLengthContentTest() {
feature_list_.InitWithFeatureState(
features::kAutofillUseTwoDotsForLastFourDigits, GetParam());
}
~AutofillCreditCardSuggestionIOSObfuscationLengthContentTest() override =
default;
int expected_obfuscation_length() const { return GetParam() ? 2 : 4; }
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
AutofillCreditCardSuggestionContentTest,
AutofillCreditCardSuggestionIOSObfuscationLengthContentTest,
testing::Bool());
TEST_P(AutofillCreditCardSuggestionIOSObfuscationLengthContentTest,
CreateCreditCardSuggestion_CorrectObfuscationLength) {
CreditCard server_card = CreateServerCard();
// Name field suggestion.
Suggestion card_name_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NAME_FULL,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
EXPECT_THAT(card_name_field_suggestion,
EqualLabels({{CreditCard::GetObfuscatedStringForCardDigits(
expected_obfuscation_length(), u"1111")}}));
// Card number field suggestion.
Suggestion card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
EXPECT_EQ(
card_number_field_suggestion.main_text.value,
base::StrCat({u"Visa ", CreditCard::GetObfuscatedStringForCardDigits(
expected_obfuscation_length(), u"1111")}));
}
#endif // BUILDFLAG(IS_IOS)
#if !BUILDFLAG(IS_ANDROID)
// The 2 boolean params denote if kAutofillEnableVcnGrayOutForMerchantOptOut
// is turned on and if merchant accepts VCN.
class AutofillCreditCardSuggestionContentVcnMerchantOptOutTest
: public AutofillCreditCardSuggestionContentTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
bool is_flag_enabled() { return std::get<0>(GetParam()); }
bool is_merchant_opted_out() { return std::get<1>(GetParam()); }
int expected_message_id() {
return is_flag_enabled() && is_merchant_opted_out()
? IDS_AUTOFILL_VIRTUAL_CARD_DISABLED_SUGGESTION_OPTION_VALUE
: IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE;
}
private:
void SetUp() override {
AutofillCreditCardSuggestionContentTest::SetUp();
scoped_feature_list_.InitWithFeatureState(
features::kAutofillEnableVcnGrayOutForMerchantOptOut,
is_flag_enabled());
ON_CALL(*static_cast<MockAutofillOptimizationGuide*>(
autofill_client()->GetAutofillOptimizationGuide()),
ShouldBlockFormFieldSuggestion)
.WillByDefault(testing::Return(is_merchant_opted_out()));
}
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
AutofillCreditCardSuggestionContentVcnMerchantOptOutTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Verify that the suggestion's texts are populated correctly for a virtual
// card suggestion when the cardholder name field is focused and the merchant
// has opted-out of virtual cards.
TEST_P(
AutofillCreditCardSuggestionContentVcnMerchantOptOutTest,
CreateCreditCardSuggestion_VirtualCardMetadata_MerchantOptOut_NameField) {
CreditCard server_card = test::GetVirtualCard();
// Name field suggestion for virtual cards.
Suggestion virtual_card_name_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NAME_FULL,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
// `is_acceptable` is false only when the flag is enabled and merchant has
// opted out of VCN.
EXPECT_EQ(virtual_card_name_field_suggestion.is_acceptable,
(!is_merchant_opted_out() || !is_flag_enabled()));
// `apply_deactivated_style` is true only when flag is enabled and merchant
// has opted out of VCN.
EXPECT_EQ(virtual_card_name_field_suggestion.apply_deactivated_style,
(is_merchant_opted_out() && is_flag_enabled()));
// The virtual card text should be populated in the labels to be shown in
// a new line.
ASSERT_EQ(virtual_card_name_field_suggestion.labels[1].size(), 1U);
EXPECT_EQ(virtual_card_name_field_suggestion.labels[1][0].value,
l10n_util::GetStringUTF16(expected_message_id()));
}
// Verify that the suggestion's texts are populated correctly for a virtual
// card suggestion when the card number field is focused and merchant has
// opted-out of virtual cards.
TEST_P(
AutofillCreditCardSuggestionContentVcnMerchantOptOutTest,
CreateCreditCardSuggestion_VirtualCardMetadata_MerchantOptOut_NumberField) {
CreditCard server_card = test::GetVirtualCard();
// Card number field suggestion for virtual cards.
Suggestion virtual_card_number_field_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
// `is_acceptable` is false only when flag is enabled and merchant has opted
// out of VCN.
EXPECT_EQ(virtual_card_number_field_suggestion.is_acceptable,
(!is_merchant_opted_out() || !is_flag_enabled()));
// `apply_deactivated_style` is true only when flag is enabled and merchant
// has opted out of VCN.
EXPECT_EQ(virtual_card_number_field_suggestion.apply_deactivated_style,
(is_merchant_opted_out() && is_flag_enabled()));
// For Desktop/Android dropdown, and on iOS, "Virtual card" is the label.
EXPECT_THAT(
virtual_card_number_field_suggestion,
EqualLabels({{l10n_util::GetStringUTF16(expected_message_id())}}));
}
#endif // BUILDFLAG(!IS_ANDROID)
class AutofillSuggestionGeneratorTestForMetadata
: public AutofillSuggestionGeneratorTest,
public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
public:
AutofillSuggestionGeneratorTestForMetadata() {
feature_list_card_product_description_.InitWithFeatureState(
features::kAutofillEnableCardProductName, std::get<0>(GetParam()));
feature_list_card_art_image_.InitWithFeatureState(
features::kAutofillEnableCardArtImage, std::get<1>(GetParam()));
}
~AutofillSuggestionGeneratorTestForMetadata() override = default;
bool card_product_description_enabled() const {
return std::get<0>(GetParam());
}
bool card_art_image_enabled() const { return std::get<1>(GetParam()); }
bool card_has_capital_one_icon() const { return std::get<2>(GetParam()); }
private:
base::test::ScopedFeatureList feature_list_card_product_description_;
base::test::ScopedFeatureList feature_list_card_art_image_;
};
INSTANTIATE_TEST_SUITE_P(All,
AutofillSuggestionGeneratorTestForMetadata,
testing::Combine(testing::Bool(),
testing::Bool(),
testing::Bool()));
TEST_P(AutofillSuggestionGeneratorTestForMetadata,
CreateCreditCardSuggestion_ServerCard) {
// Create a server card.
CreditCard server_card = CreateServerCard();
GURL card_art_url = GURL("https://www.example.com/card-art");
server_card.set_card_art_url(card_art_url);
gfx::Image fake_image = CustomIconForTest();
personal_data().test_payments_data_manager().AddCardArtImage(card_art_url,
fake_image);
Suggestion virtual_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
EXPECT_EQ(virtual_card_suggestion.type,
SuggestionType::kVirtualCreditCardEntry);
EXPECT_EQ(virtual_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
EXPECT_EQ(VerifyCardArtImageExpectation(virtual_card_suggestion, card_art_url,
fake_image),
card_art_image_enabled());
Suggestion real_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
EXPECT_EQ(real_card_suggestion.type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
EXPECT_EQ(VerifyCardArtImageExpectation(real_card_suggestion, card_art_url,
fake_image),
card_art_image_enabled());
}
TEST_P(AutofillSuggestionGeneratorTestForMetadata,
CreateCreditCardSuggestion_LocalCard_NoServerDuplicate) {
// Create a local card.
CreditCard local_card = CreateLocalCard();
Suggestion real_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(local_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
EXPECT_EQ(real_card_suggestion.type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
EXPECT_TRUE(VerifyCardArtImageExpectation(real_card_suggestion, GURL(),
gfx::Image()));
}
TEST_P(AutofillSuggestionGeneratorTestForMetadata,
CreateCreditCardSuggestion_LocalCard_ServerDuplicate) {
// Create a server card.
CreditCard server_card =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
GURL card_art_url = GURL("https://www.example.com/card-art");
server_card.set_card_art_url(card_art_url);
gfx::Image fake_image = CustomIconForTest();
personal_data().AddServerCreditCard(server_card);
personal_data().test_payments_data_manager().AddCardArtImage(card_art_url,
fake_image);
// Create a local card with same information.
CreditCard local_card =
CreateLocalCard(/*guid=*/"00000000-0000-0000-0000-000000000002");
Suggestion virtual_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(local_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/false);
EXPECT_EQ(virtual_card_suggestion.type,
SuggestionType::kVirtualCreditCardEntry);
EXPECT_EQ(virtual_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
EXPECT_EQ(VerifyCardArtImageExpectation(virtual_card_suggestion, card_art_url,
fake_image),
card_art_image_enabled());
Suggestion real_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(local_card, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/false);
EXPECT_EQ(real_card_suggestion.type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000002")));
EXPECT_EQ(VerifyCardArtImageExpectation(real_card_suggestion, card_art_url,
fake_image),
card_art_image_enabled());
}
// Verifies that the `metadata_logging_context` is correctly set.
TEST_P(AutofillSuggestionGeneratorTestForMetadata,
GetSuggestionsForCreditCards_MetadataLoggingContext) {
{
// Create one server card with no metadata.
CreditCard server_card = CreateServerCard();
server_card.set_issuer_id(kCapitalOneCardIssuerId);
if (card_has_capital_one_icon()) {
server_card.set_card_art_url(GURL(kCapitalOneCardArtUrl));
}
personal_data().AddServerCreditCard(server_card);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_TRUE(
metadata_logging_context.instruments_with_metadata_available.empty());
EXPECT_FALSE(metadata_logging_context.card_product_description_shown);
EXPECT_FALSE(metadata_logging_context.card_art_image_shown);
// Verify that a record is added that a Capital One card suggestion
// was generated, and it did not have metadata.
base::flat_map<std::string, bool>
expected_issuer_or_network_to_metadata_availability = {
{server_card.issuer_id(), false}, {server_card.network(), false}};
EXPECT_EQ(
metadata_logging_context.issuer_or_network_to_metadata_availability,
expected_issuer_or_network_to_metadata_availability);
}
personal_data().test_payments_data_manager().ClearCreditCards();
{
// Create a server card with card product description & card art image.
CreditCard server_card_with_metadata = CreateServerCard();
server_card_with_metadata.set_issuer_id(kCapitalOneCardIssuerId);
server_card_with_metadata.set_product_description(u"product_description");
server_card_with_metadata.set_card_art_url(
GURL("https://www.example.com/card-art.png"));
personal_data().AddServerCreditCard(server_card_with_metadata);
bool with_offer;
bool with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, with_offer, with_cvc,
metadata_logging_context);
EXPECT_TRUE(
metadata_logging_context.instruments_with_metadata_available.contains(
server_card_with_metadata.instrument_id()));
EXPECT_EQ(metadata_logging_context.card_product_description_shown,
card_product_description_enabled());
EXPECT_EQ(metadata_logging_context.card_art_image_shown,
card_art_image_enabled());
// Verify that a record is added that a Capital One card suggestion
// was generated, and it had metadata.
base::flat_map<std::string, bool> expected_issuer_to_metadata_availability =
{{server_card_with_metadata.issuer_id(), true},
{server_card_with_metadata.network(), true}};
EXPECT_EQ(
metadata_logging_context.issuer_or_network_to_metadata_availability,
expected_issuer_to_metadata_availability);
}
}
// TODO(crbug.com/332595462): Improve card art url unittest coverage to include
// potential edge cases.
// Verifies that the custom icon is set correctly. The card art should be shown
// when the metadata card art flag is enabled. Capital One virtual card icon is
// an exception which should only and always be shown for virtual cards.
TEST_P(AutofillSuggestionGeneratorTestForMetadata,
GetSuggestionsForCreditCards_CustomCardIcon) {
// Create a server card.
CreditCard server_card = CreateServerCard();
GURL card_art_url =
GURL(card_has_capital_one_icon() ? kCapitalOneCardArtUrl
: "https://www.example.com/card-art");
server_card.set_card_art_url(card_art_url);
server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
gfx::Image fake_image = CustomIconForTest();
personal_data().AddServerCreditCard(server_card);
personal_data().test_payments_data_manager().AddCardArtImage(card_art_url,
fake_image);
bool unused_with_offer;
bool unused_with_cvc;
autofill_metrics::CardMetadataLoggingContext metadata_logging_context;
std::vector<Suggestion> suggestions =
suggestion_generator().GetSuggestionsForCreditCards(
FormFieldData(), CREDIT_CARD_NUMBER, kDefaultTriggerSource,
/*should_show_scan_credit_card=*/false,
/*should_show_cards_from_account=*/false, unused_with_offer,
unused_with_cvc, metadata_logging_context);
// Suggestions in `suggestions` are persisted in order of their presentation
// to the user in the Autofill dropdown and currently virtual cards are shown
// before their associated FPAN suggestion.
Suggestion virtual_card_suggestion = suggestions[0];
Suggestion fpan_card_suggestion = suggestions[1];
// Verify that for virtual cards, the custom icon is shown if the card art is
// the Capital One virtual card art or if the metadata card art is enabled.
EXPECT_EQ(VerifyCardArtImageExpectation(virtual_card_suggestion, card_art_url,
fake_image),
card_has_capital_one_icon() || card_art_image_enabled());
// Verify that for FPAN, the custom icon is shown if the card art is not the
// Capital One virtual card art and the metadata card art is enabled.
EXPECT_EQ(VerifyCardArtImageExpectation(fpan_card_suggestion, card_art_url,
fake_image),
!card_has_capital_one_icon() && card_art_image_enabled());
}
class AutofillSuggestionGeneratorTestForOffer
: public AutofillSuggestionGeneratorTest,
public testing::WithParamInterface<bool> {
public:
AutofillSuggestionGeneratorTestForOffer() {
#if BUILDFLAG(IS_ANDROID)
keyboard_accessory_offer_enabled_ = GetParam();
if (keyboard_accessory_offer_enabled_) {
scoped_feature_keyboard_accessory_offer_.InitWithFeatures(
{features::kAutofillEnableOffersInClankKeyboardAccessory}, {});
} else {
scoped_feature_keyboard_accessory_offer_.InitWithFeatures(
{}, {features::kAutofillEnableOffersInClankKeyboardAccessory});
}
#endif
}
~AutofillSuggestionGeneratorTestForOffer() override = default;
bool keyboard_accessory_offer_enabled() {
#if BUILDFLAG(IS_ANDROID)
return keyboard_accessory_offer_enabled_;
#else
return false;
#endif
}
#if BUILDFLAG(IS_ANDROID)
private:
bool keyboard_accessory_offer_enabled_;
base::test::ScopedFeatureList scoped_feature_keyboard_accessory_offer_;
#endif
};
INSTANTIATE_TEST_SUITE_P(All,
AutofillSuggestionGeneratorTestForOffer,
testing::Bool());
// Test to make sure the suggestion gets populated with the right content if the
// card has card linked offer available.
TEST_P(AutofillSuggestionGeneratorTestForOffer,
CreateCreditCardSuggestion_ServerCardWithOffer) {
// Create a server card.
CreditCard server_card1 =
CreateServerCard(/*guid=*/"00000000-0000-0000-0000-000000000001");
Suggestion virtual_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card1, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/true,
/*card_linked_offer_available=*/true);
EXPECT_EQ(virtual_card_suggestion.type,
SuggestionType::kVirtualCreditCardEntry);
EXPECT_EQ(virtual_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
// Ensures CLO text is not shown for virtual card option.
EXPECT_EQ(virtual_card_suggestion.labels.size(), 1U);
Suggestion real_card_suggestion =
test_api(suggestion_generator())
.CreateCreditCardSuggestion(server_card1, CREDIT_CARD_NUMBER,
/*virtual_card_option=*/false,
/*card_linked_offer_available=*/true);
EXPECT_EQ(real_card_suggestion.type, SuggestionType::kCreditCardEntry);
EXPECT_EQ(real_card_suggestion.GetPayload<Suggestion::BackendId>(),
Suggestion::BackendId(
Suggestion::Guid("00000000-0000-0000-0000-000000000001")));
if (keyboard_accessory_offer_enabled()) {
#if BUILDFLAG(IS_ANDROID)
EXPECT_EQ(real_card_suggestion.labels.size(), 1U);
EXPECT_EQ(real_card_suggestion.feature_for_iph,
&feature_engagement::kIPHKeyboardAccessoryPaymentOfferFeature);
#endif
} else {
ASSERT_EQ(real_card_suggestion.labels.size(), 2U);
ASSERT_EQ(real_card_suggestion.labels[1].size(), 1U);
EXPECT_EQ(real_card_suggestion.labels[1][0].value,
l10n_util::GetStringUTF16(IDS_AUTOFILL_OFFERS_CASHBACK));
}
}
} // namespace autofill