chromium/components/autofill/ios/browser/autofill_agent_unittests.mm

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

#import "components/autofill/ios/browser/autofill_agent.h"

#import <string>

#import "base/apple/bundle_locations.h"
#import "base/json/json_writer.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/weak_ptr.h"
#import "base/strings/strcat.h"
#import "base/strings/string_number_conversions.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/gtest_util.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/test_timeouts.h"
#import "base/values.h"
#import "components/autofill/core/browser/autofill_test_utils.h"
#import "components/autofill/core/browser/data_model/credit_card.h"
#import "components/autofill/core/browser/filling_product.h"
#import "components/autofill/core/browser/test_autofill_client.h"
#import "components/autofill/core/browser/ui/mock_autofill_suggestion_delegate.h"
#import "components/autofill/core/browser/ui/suggestion.h"
#import "components/autofill/core/browser/ui/suggestion_type.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/core/common/field_data_manager.h"
#import "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_field_data.h"
#import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h"
#import "components/autofill/ios/browser/password_autofill_agent.h"
#import "components/autofill/ios/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
#import "components/autofill/ios/form_util/form_util_java_script_feature.h"
#import "components/prefs/pref_service.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/task_observer_util.h"
#import "ios/web/public/test/web_test.h"
#import "services/metrics/public/cpp/ukm_builders.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "third_party/abseil-cpp/absl/types/variant.h"
#import "ui/base/resource/resource_bundle.h"
#import "ui/gfx/image/image_unittest_util.h"
#import "url/gurl.h"

using autofill::AutofillJavaScriptFeature;
using autofill::FieldDataManager;
using autofill::FieldRendererId;
using autofill::FillingProduct;
using autofill::FormRendererId;
using autofill::SuggestionType;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace {

using autofill::AutofillDriverIOS;
using autofill::AutofillDriverIOSFactory;

// Returns the minimal FormData content for testing filling.
std::vector<autofill::FormFieldData::FillData>
MinimalFormFieldDataForFilling() {
  autofill::FormFieldData field;
  field.set_value(u"test-username");
  field.set_host_form_id(FormRendererId(1));
  field.set_renderer_id(FieldRendererId(2));
  field.set_is_autofilled(true);
  return {autofill::FormFieldData::FillData(std::move(field))};
}

// Returns a simple form suggestion that only consists of a `value` and a `type`
FormSuggestion* SimpleFormSuggestion(std::u16string value,
                                     autofill::SuggestionType type) {
  return [FormSuggestion suggestionWithValue:base::SysUTF16ToNSString(value)
                          displayDescription:@""
                                        icon:nil
                                        type:type
                           backendIdentifier:@""
                              requiresReauth:NO];
}

}  // namespace

@interface AutofillAgent (Testing)
- (void)updateFieldManagerWithFillingResults:(NSString*)jsonString
                                     inFrame:(web::WebFrame*)frame;
@end

// Test fixture for AutofillAgent testing.
class AutofillAgentTests : public web::WebTest {
 public:
  AutofillAgentTests() {}

  AutofillAgentTests(const AutofillAgentTests&) = delete;
  AutofillAgentTests& operator=(const AutofillAgentTests&) = delete;

  void AddWebFrame(std::unique_ptr<web::WebFrame> frame) {
    fake_web_frames_manager_->AddWebFrame(std::move(frame));
  }

  void RemoveWebFrame(const std::string& frame_id) {
    fake_web_frames_manager_->RemoveWebFrame(frame_id);
  }

  void SetUp() override {
    web::WebTest::SetUp();

    OverrideJavaScriptFeatures(
        {autofill::AutofillJavaScriptFeature::GetInstance(),
         autofill::FormHandlersJavaScriptFeature::GetInstance(),
         autofill::FormUtilJavaScriptFeature::GetInstance()});

    fake_web_state_.SetBrowserState(GetBrowserState());
    fake_web_state_.SetContentIsHTML(true);
    auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
    fake_web_frames_manager_ = frames_manager.get();
    web::ContentWorld content_world =
        AutofillJavaScriptFeature::GetInstance()->GetSupportedContentWorld();
    fake_web_state_.SetWebFramesManager(content_world,
                                        std::move(frames_manager));

    GURL url("https://example.com");
    fake_web_state_.SetCurrentURL(url);
    auto main_frame = web::FakeWebFrame::Create("frameID", true, url);
    main_frame->set_browser_state(GetBrowserState());
    fake_main_frame_ = main_frame.get();
    AddWebFrame(std::move(main_frame));

    autofill::PasswordAutofillAgent::CreateForWebState(&fake_web_state_,
                                                       &delegate_mock_);

    prefs_ = autofill::test::PrefServiceForTesting();
    autofill::prefs::SetAutofillProfileEnabled(prefs_.get(), true);
    autofill::prefs::SetAutofillPaymentMethodsEnabled(prefs_.get(), true);
    autofill_agent_ =
        [[AutofillAgent alloc] initWithPrefService:prefs_.get()
                                          webState:&fake_web_state_];
  }

  std::unique_ptr<web::FakeWebFrame> CreateMainWebFrame() {
    std::unique_ptr<web::FakeWebFrame> frame =
        web::FakeWebFrame::CreateMainWebFrame(GURL());
    frame->set_browser_state(GetBrowserState());
    return frame;
  }

  std::unique_ptr<web::FakeWebFrame> CreateChildWebFrame() {
    std::unique_ptr<web::FakeWebFrame> frame =
        web::FakeWebFrame::CreateChildWebFrame(GURL());
    frame->set_browser_state(GetBrowserState());
    return frame;
  }

  // The prefs_ must outlive the fake_web_state_ and the autofill_agent_,
  // the latter of which can be de-allocated as part of de-allocating the
  // fake_web_state_.
  std::unique_ptr<PrefService> prefs_;
  // The client_ needs to outlive the fake_web_state_, which owns the
  // frames.
  autofill::TestAutofillClient client_;
  web::FakeWebState fake_web_state_;
  raw_ptr<web::FakeWebFrame> fake_main_frame_ = nullptr;
  raw_ptr<web::FakeWebFramesManager> fake_web_frames_manager_ = nullptr;
  AutofillAgent* autofill_agent_;
  autofill::MockPasswordAutofillAgentDelegate delegate_mock_;
};

// Tests that form's name and fields' identifiers, values, and whether they are
// autofilled are sent to the JS. Fields with empty values and those that are
// not autofilled are skipped. Tests logic based on renderer ids usage.
TEST_F(AutofillAgentTests,
       OnFormDataFilledTestWithFrameMessagingUsingRendererIDs) {
  std::string locale("en");
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              locale);

  std::vector<autofill::FormFieldData::FillData> fill_data;
  autofill::FormFieldData field;
  field.set_form_control_type(autofill::FormControlType::kInputText);
  field.set_label(u"Card number");
  field.set_name(u"number");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"number");
  field.set_value(u"number_value");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(2));
  fill_data.push_back(autofill::FormFieldData::FillData(field));
  field.set_label(u"Name on Card");
  field.set_name(u"name");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"name");
  field.set_value(u"name_value");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(3));
  fill_data.push_back(autofill::FormFieldData::FillData(field));
  field.set_label(u"Expiry Month");
  field.set_name(u"expiry_month");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"expiry_month");
  field.set_value(u"01");
  field.set_is_autofilled(false);
  field.set_renderer_id(FieldRendererId(4));
  fill_data.push_back(autofill::FormFieldData::FillData(field));
  field.set_label(u"Unknown field");
  field.set_name(u"unknown");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"unknown");
  field.set_value(u"");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(5));
  fill_data.push_back(autofill::FormFieldData::FillData(field));

  [autofill_agent_ fillData:fill_data
                    inFrame:fake_web_frames_manager_->GetMainWebFrame()];
  fake_web_state_.WasShown();

  EXPECT_EQ(u"__gCrWeb.autofill.fillForm({\"fields\":{\"2\":{\"hostFormId\":0,"
            u"\"section\":\"-default\",\"value\":\"number_value\"},\"3\":{"
            u"\"hostFormId\":0,\"section\":\"-default\","
            u"\"value\":\"name_value\"}}}, 0);",
            fake_main_frame_->GetLastJavaScriptCall());
}

// Tests that `fillSpecificFormField` in `autofill_agent_` dispatches the
// correct javascript call to the autofill controller.
TEST_F(AutofillAgentTests, FillSpecificFormField) {
  std::string locale("en");
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              locale);

  autofill::FormFieldData field;
  field.set_form_control_type(autofill::FormControlType::kInputText);
  field.set_label(u"Card number");
  field.set_name(u"number");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"number");
  field.set_value(u"number_value");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(2));

  [autofill_agent_
      fillSpecificFormField:field.renderer_id()
                  withValue:u"mattwashere"
                    inFrame:fake_web_frames_manager_->GetMainWebFrame()];
  fake_web_state_.WasShown();
  EXPECT_EQ(u"__gCrWeb.autofill.fillSpecificFormField({\"renderer_id\":"
            u"2,\"value\":\"mattwashere\"});",
            fake_main_frame_->GetLastJavaScriptCall());
}

// Test that the updates are applied when filling specific form field is done
// successfully.
TEST_F(AutofillAgentTests,
       FillSpecificFormField_UpdateWithResults_WhenSuccess) {
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              "en");

  std::vector<autofill::FormFieldData::FillData> fields =
      MinimalFormFieldDataForFilling();
  const std::u16string& field_value = fields[0].value;
  const FieldRendererId field_id = fields[0].renderer_id;

  // Set the result returned from filling.
  base::Value result(true);
  fake_main_frame_->AddJsResultForFunctionCall(
      &result, "autofill.fillSpecificFormField");

  EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(),
                                           std::optional<FormRendererId>(),
                                           field_id, field_value));

  // Declare the page as shown to allow filling.
  fake_web_state_.WasShown();

  // Fill form data.
  [autofill_agent_ fillSpecificFormField:field_id
                               withValue:field_value
                                 inFrame:fake_main_frame_];

  // Run queues to yield the filling results.
  web::test::WaitForBackgroundTasks();

  // Check that the field value update was propagated to the FieldDataManager of
  // the web frame.
  FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field_id));
}

// Test that the updates aren't applied when filling specific form field has
// failed.
TEST_F(AutofillAgentTests,
       FillSpecificFormField_UpdateWithResults_WhenFailure) {
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              "en");

  std::vector<autofill::FormFieldData::FillData> fields =
      MinimalFormFieldDataForFilling();
  const std::u16string& field_value = fields[0].value;
  const FieldRendererId field_id = fields[0].renderer_id;

  // Set the result returned from filling.
  base::Value result(false);
  fake_main_frame_->AddJsResultForFunctionCall(
      &result, "autofill.fillSpecificFormField");

  EXPECT_CALL(delegate_mock_, DidFillField).Times(0);

  // Declare the page as shown to allow filling.
  fake_web_state_.WasShown();

  // Fill form data.
  [autofill_agent_ fillSpecificFormField:field_id
                               withValue:field_value
                                 inFrame:fake_main_frame_];

  // Run queues to yield the filling results.
  web::test::WaitForBackgroundTasks();

  // Check that the field value update was not propagated to the
  // FieldDataManager.
  FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field_id));
}

// Tests that `ApplyFieldAction` in `AutofillDriverIOS` dispatches the
// correct javascript call to the autofill controller.
TEST_F(AutofillAgentTests, DriverFillSpecificFormField) {
  std::string locale("en");
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_,
                                              autofill_agent_, locale);

  autofill::FormFieldData field;
  field.set_form_control_type(autofill::FormControlType::kInputText);
  field.set_label(u"Card number");
  field.set_name(u"number");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"number");
  field.set_value(u"number_value");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(2));

  AutofillDriverIOS* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(
          &fake_web_state_, fake_web_frames_manager_->GetMainWebFrame());
  main_frame_driver->ApplyFieldAction(
      autofill::mojom::FieldActionType::kReplaceAll,
      autofill::mojom::ActionPersistence::kFill, field.global_id(),
      u"mattwashere");

  fake_web_state_.WasShown();
  EXPECT_EQ(u"__gCrWeb.autofill.fillSpecificFormField({\"renderer_id\":"
            u"2,\"value\":\"mattwashere\"});",
            fake_main_frame_->GetLastJavaScriptCall());
}

// Tests that `ApplyFieldAction` with `ActionPersistence::kPreview`in
// `AutofillDriverIOS` does not dispatch a JS call.
TEST_F(AutofillAgentTests, DriverPreviewSpecificFormField) {
  std::string locale("en");
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_,
                                              autofill_agent_, locale);

  autofill::FormFieldData field;
  field.set_form_control_type(autofill::FormControlType::kInputText);
  field.set_label(u"Card number");
  field.set_name(u"number");
  field.set_name_attribute(field.name());
  field.set_id_attribute(u"number");
  field.set_value(u"number_value");
  field.set_is_autofilled(true);
  field.set_renderer_id(FieldRendererId(2));

  AutofillDriverIOS* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(
          &fake_web_state_, fake_web_frames_manager_->GetMainWebFrame());
  // Preview is not currently supported; no JS should be run.
  main_frame_driver->ApplyFieldAction(
      autofill::mojom::FieldActionType::kReplaceAll,
      autofill::mojom::ActionPersistence::kPreview, field.global_id(),
      u"mattwashere");

  fake_web_state_.WasShown();
  EXPECT_EQ(u"", fake_main_frame_->GetLastJavaScriptCall());
}

// Tests that when a non user initiated form activity is registered the
// completion callback passed to the call to check if suggestions are available
// is invoked with no suggestions.
TEST_F(AutofillAgentTests,
       CheckIfSuggestionsAvailable_NonUserInitiatedActivity) {
  __block BOOL completion_handler_success = NO;
  __block BOOL completion_handler_called = NO;

  FormSuggestionProviderQuery* form_query =
      [[FormSuggestionProviderQuery alloc] initWithFormName:@"form"
                                             formRendererID:FormRendererId(1)
                                            fieldIdentifier:@"address"
                                            fieldRendererID:FieldRendererId(2)
                                                  fieldType:@"text"
                                                       type:@"focus"
                                                 typedValue:@""
                                                    frameID:@"frameID"];
  [autofill_agent_ checkIfSuggestionsAvailableForForm:form_query
                                       hasUserGesture:NO
                                             webState:&fake_web_state_
                                    completionHandler:^(BOOL success) {
                                      completion_handler_success = success;
                                      completion_handler_called = YES;
                                    }];
  fake_web_state_.WasShown();

  // Wait until the expected handler is called.
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() {
        return completion_handler_called;
      }));
  EXPECT_FALSE(completion_handler_success);
}

// Tests that "Show credit cards from account" opt-in is shown.
TEST_F(AutofillAgentTests, onSuggestionsReady_ShowAccountCards) {
  __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil;
  __block BOOL completion_handler_called = NO;

  autofill::MockAutofillSuggestionDelegate mock_delegate;
  EXPECT_CALL(mock_delegate, OnSuggestionsShown);

  // Make the suggestions available to AutofillAgent.
  std::vector<autofill::Suggestion> autofillSuggestions;
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           SuggestionType::kShowAccountCards));
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];

  // Retrieves the suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_suggestions = [suggestions copy];
    completion_handler_called = YES;
  };
  FormSuggestionProviderQuery* form_query =
      [[FormSuggestionProviderQuery alloc] initWithFormName:@"form"
                                             formRendererID:FormRendererId(1)
                                            fieldIdentifier:@"address"
                                            fieldRendererID:FieldRendererId(2)
                                                  fieldType:@"text"
                                                       type:@"focus"
                                                 typedValue:@""
                                                    frameID:@"frameID"];
  [autofill_agent_ retrieveSuggestionsForForm:form_query
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  fake_web_state_.WasShown();

  // Wait until the expected handler is called.
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() {
        return completion_handler_called;
      }));

  // "Show credit cards from account" should be the only suggestion.
  EXPECT_EQ(1U, completion_handler_suggestions.count);
  EXPECT_EQ(SuggestionType::kShowAccountCards,
            completion_handler_suggestions[0].type);
}

// Tests that virtual cards are being served as suggestions with the
// wanted string values of (main_text, ' ', minor_text) where the main_text
// is the 'Virtual card' string and the minor_text is the card name + last 4 or
// the card holder's name
TEST_F(AutofillAgentTests, showAutofillPopup_ShowVirtualCards) {
  base::test::ScopedFeatureList feature_list_;
  feature_list_.InitAndEnableFeature(
      autofill::features::kAutofillEnableVirtualCards);

  __block NSUInteger suggestion_array_size = 0;
  __block FormSuggestion* virtual_card_suggestion = nil;
  __block FormSuggestion* credit_card_suggestion = nil;
  UIImage* visa_icon =
      ui::ResourceBundle::GetSharedInstance()
          .GetNativeImageNamed(autofill::CreditCard::IconResourceId("visaCC"))
          .ToUIImage();
  NSString* expiration_date_display_description = base::SysUTF8ToNSString(
      autofill::test::NextMonth() + "/" + autofill::test::NextYear().substr(2));
  // Mock different popup types.
  testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate;
  EXPECT_CALL(mock_delegate, GetMainFillingProduct)
      .WillOnce(testing::Return(FillingProduct::kCreditCard))
      .WillOnce(testing::Return(FillingProduct::kCreditCard));

  const std::string expiration_date_label = base::StrCat(
      {autofill::test::NextMonth(), "/", autofill::test::NextYear().substr(2)});

  // Initialize suggestion.
  std::vector<autofill::Suggestion> autofillSuggestions = {
      autofill::Suggestion("Virtual card", "Quicksilver ••1111",
                           expiration_date_label,
                           autofill::Suggestion::Icon::kCardVisa,
                           autofill::SuggestionType::kVirtualCreditCardEntry),
      autofill::Suggestion("Quicksilver ••1111", "", expiration_date_label,
                           autofill::Suggestion::Icon::kCardVisa,
                           autofill::SuggestionType::kCreditCardEntry),
  };

  // Completion handler to retrieve suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    suggestion_array_size = suggestions.count;
    virtual_card_suggestion = [FormSuggestion
               suggestionWithValue:[suggestions[0].value copy]
                        minorValue:[suggestions[0].minorValue copy]
                displayDescription:[suggestions[0].displayDescription copy]
                              icon:[suggestions[0].icon copy]
                              type:suggestions[0].type
                 backendIdentifier:suggestions[0].backendIdentifier
                    requiresReauth:suggestions[0].requiresReauth
        acceptanceA11yAnnouncement:[suggestions[0]
                                           .acceptanceA11yAnnouncement copy]];
    credit_card_suggestion = [FormSuggestion
               suggestionWithValue:[suggestions[1].value copy]
                        minorValue:[suggestions[1].minorValue copy]
                displayDescription:[suggestions[1].displayDescription copy]
                              icon:[suggestions[1].icon copy]
                              type:suggestions[1].type
                 backendIdentifier:suggestions[1].backendIdentifier
                    requiresReauth:suggestions[1].requiresReauth
        acceptanceA11yAnnouncement:[suggestions[1]
                                           .acceptanceA11yAnnouncement copy]];
  };

  // Make credit card suggestion.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];

  // Confirm both suggestions present
  ASSERT_EQ(2U, suggestion_array_size);

  // Confirm virtual card suggestion properties
  EXPECT_NSEQ(@"Virtual card", virtual_card_suggestion.value);
  EXPECT_NSEQ(@"Quicksilver ••1111", virtual_card_suggestion.minorValue);
  EXPECT_NSEQ(expiration_date_display_description,
              virtual_card_suggestion.displayDescription);
  EXPECT_TRUE(
      gfx::test::PlatformImagesEqual(virtual_card_suggestion.icon, visa_icon));
  EXPECT_EQ(autofill::SuggestionType::kVirtualCreditCardEntry,
            virtual_card_suggestion.type);
  EXPECT_NSEQ(@"", virtual_card_suggestion.backendIdentifier);
  EXPECT_EQ(false, virtual_card_suggestion.requiresReauth);
  EXPECT_NSEQ(nil, virtual_card_suggestion.acceptanceA11yAnnouncement);

  // Confirm credit card suggestion properties
  EXPECT_NSEQ(@"Quicksilver ••1111", credit_card_suggestion.value);
  EXPECT_NSEQ(nil, credit_card_suggestion.minorValue);
  EXPECT_NSEQ(expiration_date_display_description,
              credit_card_suggestion.displayDescription);
  EXPECT_TRUE(
      gfx::test::PlatformImagesEqual(credit_card_suggestion.icon, visa_icon));
  EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry,
            credit_card_suggestion.type);
  EXPECT_NSEQ(@"", credit_card_suggestion.backendIdentifier);
  EXPECT_EQ(false, credit_card_suggestion.requiresReauth);
  EXPECT_NSEQ(nil, credit_card_suggestion.acceptanceA11yAnnouncement);
}

// Tests that only credit card suggestions would have icons.
TEST_F(AutofillAgentTests,
       showAutofillPopup_ShowIconForCreditCardSuggestionsOnly) {
  __block UIImage* completion_handler_icon = nil;

  // Mock different popup types.
  testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate;
  EXPECT_CALL(mock_delegate, GetMainFillingProduct)
      .WillOnce(testing::Return(FillingProduct::kCreditCard))
      .WillOnce(testing::Return(FillingProduct::kAddress))
      .WillOnce(testing::Return(FillingProduct::kNone));
  // Initialize suggestion.
  std::vector<autofill::Suggestion> autofillSuggestions = {
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kCardVisa,
                           autofill::SuggestionType::kCreditCardEntry),
      // This suggestion has a valid credit card icon, but the Suggestion type
      // (kShowAccountCards) is wrong.
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kCardVisa,
                           autofill::SuggestionType::kShowAccountCards),
  };
  // Completion handler to retrieve suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    ASSERT_EQ(2U, suggestions.count);
    completion_handler_icon = [suggestions[0].icon copy];
    // The non-credit card suggestion should never have an icon.
    EXPECT_EQ(nil, suggestions[1].icon);
  };

  // Make credit card suggestion.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_NE(nil, completion_handler_icon);
  // Make address suggestion.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_EQ(nil, completion_handler_icon);
  // Make unspecified suggestion.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_EQ(nil, completion_handler_icon);
}

// Tests that an empty network icon in a credit card suggestion will not cause
// any problems. Regression test for crbug.com/1446933
TEST_F(AutofillAgentTests, showAutofillPopup_EmptyIconInCreditCardSuggestion) {
  // Deliberately initialize this as non-nil, as we are expecting it to be set
  // to nil by the test.
  __block UIImage* completion_handler_icon = gfx::test::CreatePlatformImage();
  ASSERT_NE(nil, completion_handler_icon);

  testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate;
  EXPECT_CALL(mock_delegate, GetMainFillingProduct)
      .WillRepeatedly(testing::Return(FillingProduct::kCreditCard));

  std::vector<autofill::Suggestion> autofillSuggestions = {
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kCreditCardEntry)};

  // Completion handler to retrieve suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_icon = [suggestions[0].icon copy];
  };

  // Make credit card suggestion.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_EQ(nil, completion_handler_icon);
}

// Verify that plus address suggestions are handled appropriately in
// `showAutofillPopup`.
TEST_F(AutofillAgentTests, showAutofillPopup_PlusAddresses) {
  __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil;
  __block BOOL completion_handler_called = NO;
  testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate;

  const std::string createSuggestionText = "create";
  const std::string fillExistingSuggestionText = "existing";
  std::vector<autofill::Suggestion> autofillSuggestions = {
      autofill::Suggestion(createSuggestionText, "",
                           autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kCreateNewPlusAddress),
      autofill::Suggestion(fillExistingSuggestionText, "",
                           autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kFillExistingPlusAddress)};

  // Completion handler to retrieve suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_suggestions = [suggestions copy];
    completion_handler_called = YES;
  };

  // Make plus address suggestions and note the conversion to `FormSuggestion`
  // objects.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];

  // Wait until the expected handler is called.
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() {
        return completion_handler_called;
      }));

  // The plus address suggestions should be handled by the conversion to
  // `FormSuggestion` objects.
  EXPECT_EQ(2U, completion_handler_suggestions.count);
  EXPECT_EQ(SuggestionType::kCreateNewPlusAddress,
            completion_handler_suggestions[0].type);
  EXPECT_NSEQ(base::SysUTF8ToNSString(createSuggestionText),
              completion_handler_suggestions[0].value);
  EXPECT_EQ(autofill::SuggestionType::kFillExistingPlusAddress,
            completion_handler_suggestions[1].type);
  EXPECT_NSEQ(base::SysUTF8ToNSString(fillExistingSuggestionText),
              completion_handler_suggestions[1].value);
}

// Tests that for credit cards, a custom icon is preferred over the default
// icon.
TEST_F(AutofillAgentTests,
       showAutofillPopup_PreferCustomIconForCreditCardSuggestions) {
  autofill::Suggestion::Icon suggestion_network_icon =
      autofill::Suggestion::Icon::kCardVisa;
  UIImage* network_icon_image =
      ui::ResourceBundle::GetSharedInstance()
          .GetNativeImageNamed(
              autofill::CreditCard::IconResourceId(suggestion_network_icon))
          .ToUIImage();
  gfx::Image custom_icon = gfx::test::CreateImage(5, 5);

  testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate;
  EXPECT_CALL(mock_delegate, GetMainFillingProduct)
      .WillRepeatedly(testing::Return(FillingProduct::kCreditCard));

  // Completion handler to retrieve suggestions.
  __block UIImage* completion_handler_icon = nil;
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_icon = [suggestions[0].icon copy];
  };

  // Initialize suggestion, initially without a custom icon.
  std::vector<autofill::Suggestion> autofillSuggestions = {
      autofill::Suggestion("", "", suggestion_network_icon,
                           autofill::SuggestionType::kCreditCardEntry)};
  ASSERT_TRUE(
      absl::holds_alternative<gfx::Image>(autofillSuggestions[0].custom_icon));
  ASSERT_TRUE(
      absl::get<gfx::Image>(autofillSuggestions[0].custom_icon).IsEmpty());

  // When the custom icon is not present, the default icon should be used.
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_TRUE(gfx::test::PlatformImagesEqual(completion_handler_icon,
                                             network_icon_image));

  // Now set a custom icon, which should override the default.
  autofillSuggestions[0].custom_icon = custom_icon;
  [autofill_agent_ showAutofillPopup:autofillSuggestions
                  suggestionDelegate:mock_delegate.GetWeakPtr()];
  [autofill_agent_ retrieveSuggestionsForForm:nil
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  EXPECT_TRUE(gfx::test::PlatformImagesEqual(completion_handler_icon,
                                             custom_icon.ToUIImage()));
}

// Tests that when Autofill suggestions are made available to AutofillAgent
// "Clear Form" is moved to the start of the list and the order of other
// suggestions remains unchanged.
TEST_F(AutofillAgentTests, onSuggestionsReady_ClearForm) {
  __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil;
  __block BOOL completion_handler_called = NO;

  // Make the suggestions available to AutofillAgent.
  std::vector<autofill::Suggestion> autofillSuggestions;
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kAddressEntry));
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kAddressEntry));
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           SuggestionType::kUndoOrClear));
  [autofill_agent_
       showAutofillPopup:autofillSuggestions
      suggestionDelegate:base::WeakPtr<autofill::AutofillSuggestionDelegate>()];

  // Retrieves the suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_suggestions = [suggestions copy];
    completion_handler_called = YES;
  };
  FormSuggestionProviderQuery* form_query =
      [[FormSuggestionProviderQuery alloc] initWithFormName:@"form"
                                             formRendererID:FormRendererId(1)
                                            fieldIdentifier:@"address"
                                            fieldRendererID:FieldRendererId(2)
                                                  fieldType:@"text"
                                                       type:@"focus"
                                                 typedValue:@""
                                                    frameID:@"frameID"];
  [autofill_agent_ retrieveSuggestionsForForm:form_query
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  fake_web_state_.WasShown();

  // Wait until the expected handler is called.
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() {
        return completion_handler_called;
      }));

  // "Clear Form" should appear as the first suggestion. Otherwise, the order of
  // suggestions should not change.
  EXPECT_EQ(3U, completion_handler_suggestions.count);
  EXPECT_EQ(SuggestionType::kUndoOrClear,
            completion_handler_suggestions[0].type);
  EXPECT_EQ(autofill::SuggestionType::kAddressEntry,
            completion_handler_suggestions[1].type);
  EXPECT_EQ(autofill::SuggestionType::kAddressEntry,
            completion_handler_suggestions[2].type);
}

// Tests that when Autofill suggestions are made available to AutofillAgent
// GPay icon remains as the first suggestion.
TEST_F(AutofillAgentTests, onSuggestionsReady_ClearFormWithGPay) {
  __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil;
  __block BOOL completion_handler_called = NO;

  // Make the suggestions available to AutofillAgent.
  std::vector<autofill::Suggestion> autofillSuggestions;
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kCreditCardEntry));
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           autofill::SuggestionType::kCreditCardEntry));
  autofillSuggestions.push_back(
      autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon,
                           SuggestionType::kUndoOrClear));
  [autofill_agent_
       showAutofillPopup:autofillSuggestions
      suggestionDelegate:base::WeakPtr<autofill::AutofillSuggestionDelegate>()];

  // Retrieves the suggestions.
  auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
                             id<FormSuggestionProvider> delegate) {
    completion_handler_suggestions = [suggestions copy];
    completion_handler_called = YES;
  };
  FormSuggestionProviderQuery* form_query =
      [[FormSuggestionProviderQuery alloc] initWithFormName:@"form"
                                             formRendererID:FormRendererId(1)
                                            fieldIdentifier:@"address"
                                            fieldRendererID:FieldRendererId(2)
                                                  fieldType:@"text"
                                                       type:@"focus"
                                                 typedValue:@""
                                                    frameID:@"frameID"];
  [autofill_agent_ retrieveSuggestionsForForm:form_query
                                     webState:&fake_web_state_
                            completionHandler:completionHandler];
  fake_web_state_.WasShown();

  // Wait until the expected handler is called.
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() {
        return completion_handler_called;
      }));

  EXPECT_EQ(3U, completion_handler_suggestions.count);
  EXPECT_EQ(SuggestionType::kUndoOrClear,
            completion_handler_suggestions[0].type);
  EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry,
            completion_handler_suggestions[1].type);
  EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry,
            completion_handler_suggestions[2].type);
}

// Test that every frames are processed whatever is the order of pageloading
// callbacks. The main frame should always be processed first.
class AutofillAgentTestFrameInitializationOrderFrames
    : public AutofillAgentTests {
 public:
  void SetUp() override {
    AutofillAgentTests::SetUp();
    RemoveWebFrame(fake_main_frame_->GetFrameId());
    ASSERT_FALSE(AutofillDriverIOSFactory::FromWebState(&fake_web_state_));
    AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                                "en");
  }
};

// Both frames available, then page loaded.
TEST_F(AutofillAgentTestFrameInitializationOrderFrames,
       BothFramesAvailableThenPageLoaded) {
  fake_web_state_.SetLoading(true);
  std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame();
  web::FakeWebFrame* main_frame = main_frame_unique.get();
  AddWebFrame(std::move(main_frame_unique));
  AutofillDriverIOS* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame);
  EXPECT_TRUE(main_frame_driver->IsInAnyMainFrame());
  auto iframe_unique = CreateChildWebFrame();
  iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
    EXPECT_TRUE(main_frame_driver->is_processed());
  }));
  web::FakeWebFrame* iframe = iframe_unique.get();
  AddWebFrame(std::move(iframe_unique));
  AutofillDriverIOS* iframe_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe);
  EXPECT_FALSE(iframe_driver->IsInAnyMainFrame());
  EXPECT_FALSE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  fake_web_state_.SetLoading(false);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_TRUE(iframe_driver->is_processed());
  RemoveWebFrame(main_frame->GetFrameId());
  RemoveWebFrame(iframe->GetFrameId());
}

// Main frame available, then page loaded, then iframe available.
TEST_F(AutofillAgentTestFrameInitializationOrderFrames,
       MainFrameAvailableThenPageLoadedThenIframeAvailable) {
  std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame();
  web::FakeWebFrame* main_frame = main_frame_unique.get();
  auto* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame);
  std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame();
  iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
    EXPECT_TRUE(main_frame_driver->is_processed());
  }));
  web::FakeWebFrame* iframe = iframe_unique.get();
  auto* iframe_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe);
  fake_web_state_.SetLoading(true);
  AddWebFrame(std::move(main_frame_unique));
  EXPECT_FALSE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  fake_web_state_.SetLoading(false);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  AddWebFrame(std::move(iframe_unique));
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_TRUE(iframe_driver->is_processed());
  RemoveWebFrame(main_frame->GetFrameId());
  RemoveWebFrame(iframe->GetFrameId());
}

// Page loaded, then main frame, then iframe.
TEST_F(AutofillAgentTestFrameInitializationOrderFrames,
       PageLoadedThenMainFrameThenIframe) {
  std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame();
  web::FakeWebFrame* main_frame = main_frame_unique.get();
  auto* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame);
  std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame();
  iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
    EXPECT_TRUE(main_frame_driver->is_processed());
  }));
  web::FakeWebFrame* iframe = iframe_unique.get();
  auto* iframe_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe);
  fake_web_state_.SetLoading(true);
  fake_web_state_.SetLoading(false);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  EXPECT_FALSE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  AddWebFrame(std::move(main_frame_unique));
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  AddWebFrame(std::move(iframe_unique));
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_TRUE(iframe_driver->is_processed());
  RemoveWebFrame(main_frame->GetFrameId());
  RemoveWebFrame(iframe->GetFrameId());
}

// Page loaded, then iframe, then main frame.
TEST_F(AutofillAgentTestFrameInitializationOrderFrames,
       PageLoadedThenIframeThenMainFrame) {
  std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame();
  web::FakeWebFrame* main_frame = main_frame_unique.get();
  auto* main_frame_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame);
  std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame();
  iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
    EXPECT_TRUE(main_frame_driver->is_processed());
  }));
  web::FakeWebFrame* iframe = iframe_unique.get();
  auto* iframe_driver =
      AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe);
  fake_web_state_.SetLoading(true);
  fake_web_state_.SetLoading(false);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  EXPECT_FALSE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  AddWebFrame(std::move(iframe_unique));
  EXPECT_FALSE(main_frame_driver->is_processed());
  EXPECT_FALSE(iframe_driver->is_processed());
  AddWebFrame(std::move(main_frame_unique));
  EXPECT_TRUE(main_frame_driver->is_processed());
  EXPECT_TRUE(iframe_driver->is_processed());
  RemoveWebFrame(main_frame->GetFrameId());
  RemoveWebFrame(iframe->GetFrameId());
}

TEST_F(AutofillAgentTests, FillData_UpdateWithResults) {
  auto test_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();

  std::string locale("en");
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              locale);

  std::vector<autofill::FormFieldData::FillData> fields =
      MinimalFormFieldDataForFilling();
  const std::u16string& field_value = fields[0].value;
  const FormRendererId form_id = fields[0].host_form_id;
  const FieldRendererId field_id = fields[0].renderer_id;

  // Set the result returned from filling.
  std::string serializedResult;
  ASSERT_TRUE(base::JSONWriter::Write(
      base::Value::Dict().Set(base::NumberToString(field_id.value()),
                              base::UTF16ToUTF8(field_value)),
      &serializedResult));
  base::Value result(serializedResult);
  fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm");

  EXPECT_CALL(delegate_mock_,
              DidFillField(fake_main_frame_.get(),
                           std::make_optional<FormRendererId>(form_id),
                           field_id, field_value));

  // Declare the page as shown to allow filling.
  fake_web_state_.WasShown();

  // Fill form data.
  [autofill_agent_ fillData:fields inFrame:fake_main_frame_];

  // Run queues to yield the filling results.
  web::test::WaitForBackgroundTasks();

  // Check that the field value update was propagated to the FieldDataManager of
  // the web frame.
  FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field_id));

  // Check recorded UKM.
  auto entries = test_recorder->GetEntriesByName(
      ukm::builders::Autofill_FormFillSuccessIOS::kEntryName);
  // Expect one recorded metric.
  ASSERT_EQ(1u, entries.size());
  test_recorder->ExpectEntryMetric(entries[0], "FormFillSuccess", true);
}

// Tests that if there is an unknown field id in the results, the agent isn't
// notified.
TEST_F(AutofillAgentTests, FillData_UnknowFieldIdInResults) {
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              "en");

  std::vector<autofill::FormFieldData::FillData> fields =
      MinimalFormFieldDataForFilling();
  const FieldRendererId unknown_field_id = FieldRendererId(101);

  // Set the result returned from filling.
  std::string serializedResult;
  ASSERT_TRUE(base::JSONWriter::Write(
      base::Value::Dict().Set(base::NumberToString(unknown_field_id.value()),
                              base::UTF16ToUTF8(fields[0].value)),
      &serializedResult));
  base::Value result(serializedResult);
  fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm");

  EXPECT_CALL(delegate_mock_, DidFillField).Times(0);

  // Declare the page as shown to allow filling.
  fake_web_state_.WasShown();

  // Fill form data.
  [autofill_agent_ fillData:fields inFrame:fake_main_frame_];

  // Run queues to yield the filling results.
  web::test::WaitForBackgroundTasks();
}

// Tests selecting an autocomplete suggestion.
TEST_F(AutofillAgentTests, DidSelectSuggestion_AutocompleteEntry) {
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              /*locale=*/"en");

  FormRendererId form_id(1);
  FieldRendererId field1_id(2);
  const std::u16string field1_value = u"test-value";

  // Set the result returned from filling as a success.
  base::Value result(true);
  fake_main_frame_->AddJsResultForFunctionCall(&result,
                                               "autofill.fillActiveFormField");

  // Declare the page as shown to allow field filling.
  fake_web_state_.WasShown();

  // Select suggestion to trigger field filling.
  __block BOOL completion_handler_called = NO;
  FormSuggestion* form_suggestion = SimpleFormSuggestion(
      field1_value, autofill::SuggestionType::kAutocompleteEntry);
  [autofill_agent_ didSelectSuggestion:form_suggestion
                               atIndex:0
                                  form:@"single-username-form"
                        formRendererID:form_id
                       fieldIdentifier:@"username-field-1"
                       fieldRendererID:field1_id
                               frameID:base::SysUTF8ToNSString(
                                           fake_main_frame_->GetFrameId())
                     completionHandler:^() {
                       completion_handler_called = YES;
                     }];

  EXPECT_CALL(delegate_mock_,
              DidFillField(fake_main_frame_.get(),
                           std::make_optional<FormRendererId>(form_id),
                           field1_id, field1_value));

  // Run queues to yield the field filling results from the JS call.
  web::test::WaitForBackgroundTasks();

  // Check that the field value update was propagated to the FieldDataManager of
  // the web frame.
  FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
  EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id));

  // Check that the completion handler was called after handling the results
  // from the JS call.
  EXPECT_TRUE(completion_handler_called);
}

TEST_F(AutofillAgentTests, DidSelectSuggestion_ClearFormEntry) {
  AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_, &client_, nil,
                                              /*locale=*/"en");

  FormRendererId form_id(1);
  FieldRendererId field1_id(2);
  FieldRendererId field2_id(3);

  // Set the result returned from filling.
  std::string serializedResult;
  ASSERT_TRUE(base::JSONWriter::Write(
      base::Value::List()
          .Append(base::Value(base::NumberToString(field1_id.value())))
          .Append(base::Value(base::NumberToString(field2_id.value()))),
      &serializedResult));
  base::Value result(serializedResult);
  fake_main_frame_->AddJsResultForFunctionCall(
      &result, "autofill.clearAutofilledFields");

  // Declare the page as shown to allow field filling.
  fake_web_state_.WasShown();

  // Select suggestion to trigger field filling.
  __block BOOL completion_handler_called = NO;
  FormSuggestion* form_suggestion =
      SimpleFormSuggestion(u"", autofill::SuggestionType::kUndoOrClear);
  [autofill_agent_ didSelectSuggestion:form_suggestion
                               atIndex:0
                                  form:@"single-username-form"
                        formRendererID:form_id
                       fieldIdentifier:@"username-field-1"
                       fieldRendererID:field1_id
                               frameID:base::SysUTF8ToNSString(
                                           fake_main_frame_->GetFrameId())
                     completionHandler:^() {
                       completion_handler_called = YES;
                     }];

  EXPECT_CALL(delegate_mock_,
              DidFillField(fake_main_frame_.get(),
                           std::make_optional<FormRendererId>(form_id),
                           field1_id, ::testing::IsEmpty()));
  EXPECT_CALL(delegate_mock_,
              DidFillField(fake_main_frame_.get(),
                           std::make_optional<FormRendererId>(form_id),
                           field2_id, ::testing::IsEmpty()));

  // Run queues to yield the field filling results from the JS call.
  web::test::WaitForBackgroundTasks();

  // Check that the cleared field IDs aren't labeled as filled.
  FieldDataManager* fieldDataManager =
      autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_);
  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id));
  EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field2_id));

  // Check that the completion handler was called after handling the results
  // from the JS call.
  EXPECT_TRUE(completion_handler_called);
}