| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/autofill/autofill_profile_edit_table_view_controller.h" |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "components/autofill/core/browser/autofill_test_utils.h" |
| #import "components/autofill/core/browser/data_model/autofill_profile.h" |
| #import "components/autofill/core/browser/test_personal_data_manager.h" |
| #import "components/autofill/ios/common/features.h" |
| #import "components/strings/grit/components_strings.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_attributed_string_header_footer_item.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_multi_detail_text_item.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_button_item.h" |
| #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h" |
| #import "ios/chrome/browser/shared/ui/table_view/legacy_chrome_table_view_controller_test.h" |
| #import "ios/chrome/browser/ui/autofill/autofill_constants.h" |
| #import "ios/chrome/browser/ui/autofill/autofill_profile_edit_mediator.h" |
| #import "ios/chrome/browser/ui/autofill/autofill_profile_edit_table_view_controller.h" |
| #import "ios/chrome/browser/ui/autofill/cells/autofill_profile_edit_item.h" |
| #import "ios/chrome/browser/ui/autofill/cells/country_item.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #import "ios/chrome/grit/ios_strings.h" |
| #import "testing/gtest_mac.h" |
| #import "testing/platform_test.h" |
| #import "ui/base/l10n/l10n_util.h" |
| |
| namespace { |
| |
| const char16_t kTestSyncingEmail[] = u"test@email.com"; |
| |
| struct AutofillProfileEditTableViewControllerTestCase { |
| // Determines the objective of the prompt shown. |
| AutofillSaveProfilePromptMode prompt_mode; |
| // Yes, if the profile is an account profile. |
| bool account_profile; |
| // Yes, if the caller is from settings. |
| bool is_settings; |
| }; |
| |
| class AutofillProfileEditTableViewControllerTest |
| : public LegacyChromeTableViewControllerTest, |
| public ::testing::WithParamInterface< |
| AutofillProfileEditTableViewControllerTestCase> { |
| protected: |
| void SetUp() override { |
| LegacyChromeTableViewControllerTest::SetUp(); |
| CreateController(); |
| [controller() loadModel]; |
| CheckController(); |
| personal_data_manager_ = |
| std::make_unique<autofill::TestPersonalDataManager>(); |
| profile_ = std::make_unique<autofill::AutofillProfile>( |
| autofill::test::GetFullProfile2()); |
| autofill_profile_edit_mediator_ = [[AutofillProfileEditMediator alloc] |
| initWithDelegate:nil |
| personalDataManager:personal_data_manager_.get() |
| autofillProfile:profile_.get() |
| isMigrationPrompt:(GetParam().prompt_mode == |
| AutofillSaveProfilePromptMode::kMigrateProfile)]; |
| autofill_profile_edit_table_view_controller_ = |
| [[AutofillProfileEditTableViewController alloc] |
| initWithDelegate:autofill_profile_edit_mediator_ |
| userEmail:base::SysUTF16ToNSString(kTestSyncingEmail) |
| controller:controller() |
| settingsView:GetParam().is_settings]; |
| autofill_profile_edit_mediator_.consumer = |
| autofill_profile_edit_table_view_controller_; |
| |
| [autofill_profile_edit_table_view_controller_ |
| setAccountProfile:GetParam().account_profile]; |
| if (GetParam().prompt_mode == |
| AutofillSaveProfilePromptMode::kMigrateProfile) { |
| [autofill_profile_edit_table_view_controller_ setMigrationPrompt:YES]; |
| } |
| |
| CacheProfileDataForComparisons(); |
| [autofill_profile_edit_table_view_controller_ loadModel]; |
| if (!GetParam().is_settings) { |
| [autofill_profile_edit_table_view_controller_ |
| loadMessageAndButtonForModalIfSaveOrUpdate: |
| (GetParam().prompt_mode == |
| AutofillSaveProfilePromptMode::kUpdateProfile)]; |
| } else { |
| [autofill_profile_edit_table_view_controller_ loadFooterForSettings]; |
| } |
| } |
| |
| LegacyChromeTableViewController* InstantiateController() override { |
| return [[LegacyChromeTableViewController alloc] |
| initWithStyle:UITableViewStylePlain]; |
| } |
| |
| void CacheProfileDataForComparisons() { |
| full_name_ = |
| base::SysUTF16ToNSString(profile_->GetRawInfo(autofill::NAME_FULL)); |
| company_name_ = |
| base::SysUTF16ToNSString(profile_->GetRawInfo(autofill::COMPANY_NAME)); |
| address_home_line_1_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::ADDRESS_HOME_LINE1)); |
| address_home_line_2_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::ADDRESS_HOME_LINE2)); |
| city_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::ADDRESS_HOME_CITY)); |
| state_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::ADDRESS_HOME_STATE)); |
| zip_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::ADDRESS_HOME_ZIP)); |
| country_ = base::SysUTF16ToNSString( |
| profile_->GetInfo(autofill::ADDRESS_HOME_COUNTRY, |
| GetApplicationContext()->GetApplicationLocale())); |
| phone_home_whole_number_ = base::SysUTF16ToNSString( |
| profile_->GetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER)); |
| email_ = |
| base::SysUTF16ToNSString(profile_->GetRawInfo(autofill::EMAIL_ADDRESS)); |
| } |
| |
| NSString* GetFieldValue(int section, int index) { |
| if (index == 7) { |
| return base::apple::ObjCCastStrict<TableViewMultiDetailTextItem>( |
| GetTableViewItem(section, index)) |
| .trailingDetailText; |
| } |
| |
| return base::apple::ObjCCastStrict<AutofillProfileEditItem>( |
| GetTableViewItem(section, index)) |
| .textFieldValue; |
| } |
| |
| NSString* GetFooterTextForModal() { |
| return base::apple::ObjCCastStrict<TableViewTextItem>( |
| GetTableViewItem(0, 10)) |
| .text; |
| } |
| |
| NSAttributedString* GetErrorFooterString(int num_of_errors) { |
| NSString* error = l10n_util::GetPluralNSStringF( |
| IDS_IOS_SETTINGS_EDIT_AUTOFILL_ADDRESS_REQUIREMENT_ERROR, |
| num_of_errors); |
| |
| NSString* finalErrorString = |
| [NSString stringWithFormat: |
| @"%@\n%@", error, |
| l10n_util::GetNSStringF( |
| IDS_IOS_SETTINGS_AUTOFILL_ACCOUNT_ADDRESS_FOOTER_TEXT, |
| kTestSyncingEmail)]; |
| |
| NSMutableParagraphStyle* paragraphStyle = |
| [[NSMutableParagraphStyle alloc] init]; |
| paragraphStyle.paragraphSpacing = 12.0f; |
| |
| NSMutableAttributedString* attributedText = |
| [[NSMutableAttributedString alloc] |
| initWithString:finalErrorString |
| attributes:@{ |
| NSFontAttributeName : [UIFont |
| preferredFontForTextStyle:UIFontTextStyleFootnote], |
| NSForegroundColorAttributeName : |
| [UIColor colorNamed:kTextSecondaryColor], |
| NSParagraphStyleAttributeName : paragraphStyle |
| }]; |
| [attributedText addAttribute:NSForegroundColorAttributeName |
| value:[UIColor colorNamed:kRedColor] |
| range:NSMakeRange(0, error.length)]; |
| return attributedText; |
| } |
| |
| AutofillProfileEditTableViewController* |
| autofill_profile_edit_table_view_controller_; |
| AutofillProfileEditMediator* autofill_profile_edit_mediator_; |
| NSString* full_name_; |
| NSString* company_name_; |
| NSString* address_home_line_1_; |
| NSString* address_home_line_2_; |
| NSString* city_; |
| NSString* state_; |
| NSString* zip_; |
| NSString* country_; |
| NSString* phone_home_whole_number_; |
| NSString* email_; |
| std::unique_ptr<autofill::AutofillProfile> profile_; |
| std::unique_ptr<autofill::TestPersonalDataManager> personal_data_manager_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* No InstantiationName */, |
| AutofillProfileEditTableViewControllerTest, |
| testing::Values( |
| // Editing an account profile via settings. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/YES, |
| /*is_settings=*/YES}, |
| |
| // Editing a local profile via settings. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/NO, |
| /*is_settings=*/YES}, |
| |
| // Save Flow via Overlay UI: Editing an account profile. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/YES, |
| /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/NO, |
| /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing an account profile after showing |
| // the update prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kUpdateProfile, |
| /*account_profile=*/YES, /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile after showing the |
| // update prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kUpdateProfile, |
| /*account_profile=*/NO, /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile after showing the |
| // migration to account prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kMigrateProfile, |
| /*account_profile=*/NO, /*is_settings=*/NO})); |
| |
| } // namespace |
| |
| // Tests the items present in the view. |
| TEST_P(AutofillProfileEditTableViewControllerTest, TestItems) { |
| auto test_case = GetParam(); |
| bool multiple_sections = (test_case.is_settings && test_case.account_profile); |
| EXPECT_EQ(NumberOfSections(), multiple_sections ? 2 : 1); |
| if (test_case.account_profile || |
| test_case.prompt_mode == AutofillSaveProfilePromptMode::kMigrateProfile) { |
| EXPECT_EQ(NumberOfItemsInSection(0), test_case.is_settings ? 10 : 12); |
| } else { |
| EXPECT_EQ(NumberOfItemsInSection(0), test_case.is_settings ? 10 : 11); |
| } |
| |
| EXPECT_NSEQ(GetFieldValue(0, 0), full_name_); |
| EXPECT_NSEQ(GetFieldValue(0, 1), company_name_); |
| EXPECT_NSEQ(GetFieldValue(0, 2), address_home_line_1_); |
| EXPECT_NSEQ(GetFieldValue(0, 3), address_home_line_2_); |
| EXPECT_NSEQ(GetFieldValue(0, 4), city_); |
| EXPECT_NSEQ(GetFieldValue(0, 5), state_); |
| EXPECT_NSEQ(GetFieldValue(0, 6), zip_); |
| EXPECT_NSEQ(GetFieldValue(0, 7), country_); |
| EXPECT_NSEQ(GetFieldValue(0, 8), phone_home_whole_number_); |
| EXPECT_NSEQ(GetFieldValue(0, 9), email_); |
| |
| if (!test_case.is_settings) { |
| int index = 10; |
| if (test_case.account_profile || |
| test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kMigrateProfile) { |
| int expected_text_id = |
| (test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kUpdateProfile |
| ? IDS_IOS_SETTINGS_AUTOFILL_ACCOUNT_ADDRESS_FOOTER_TEXT |
| : IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_FOOTER); |
| // Check footer text in the save prompt. |
| EXPECT_NSEQ(GetFooterTextForModal(), |
| l10n_util::GetNSStringF(expected_text_id, kTestSyncingEmail)); |
| index = 11; |
| } |
| |
| int button_id = IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL; |
| if (test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kUpdateProfile) { |
| button_id = IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_OK_BUTTON_LABEL; |
| } else if (test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kMigrateProfile) { |
| button_id = |
| IDS_AUTOFILL_ADDRESS_MIGRATION_TO_ACCOUNT_PROMPT_OK_BUTTON_LABEL; |
| } |
| |
| // Check button in the save prompt. |
| CheckTextButtonCellButtonTextWithId(button_id, 0, index); |
| } |
| |
| // Check contents of the other section if present. |
| if (multiple_sections) { |
| EXPECT_EQ(NumberOfItemsInSection(1), 0); |
| NSString* expected_footer_text = l10n_util::GetNSStringF( |
| IDS_IOS_SETTINGS_AUTOFILL_ACCOUNT_ADDRESS_FOOTER_TEXT, |
| kTestSyncingEmail); |
| CheckSectionFooter(expected_footer_text, 1); |
| } |
| } |
| |
| // Test the contents of the view when the value requirements fail. |
| TEST_P(AutofillProfileEditTableViewControllerTest, TestRequirements) { |
| TableViewTextEditItem* city_item = |
| static_cast<TableViewTextEditItem*>(GetTableViewItem(0, 4)); |
| // Remove the city field value. |
| city_item.textFieldValue = @""; |
| [autofill_profile_edit_table_view_controller_ |
| tableViewItemDidChange:city_item]; |
| |
| auto test_case = GetParam(); |
| if (test_case.is_settings) { |
| if (test_case.account_profile) { |
| // Check the error message. |
| TableViewAttributedStringHeaderFooterItem* footer = |
| static_cast<TableViewAttributedStringHeaderFooterItem*>( |
| [[controller() tableViewModel] footerForSectionIndex:1]); |
| EXPECT_NSEQ(GetErrorFooterString(1), footer.attributedString); |
| } |
| } else { |
| // The button in the save prompt should be disabled. |
| TableViewTextButtonItem* button_item = |
| static_cast<TableViewTextButtonItem*>( |
| GetTableViewItem(0, NumberOfItemsInSection(0) - 1)); |
| EXPECT_EQ(button_item.enabled, |
| !test_case.account_profile && |
| test_case.prompt_mode != |
| AutofillSaveProfilePromptMode::kMigrateProfile); |
| |
| // Footer text remains unchanged for the save prompts. |
| if (test_case.account_profile || |
| test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kMigrateProfile) { |
| int expected_text_id = |
| (test_case.prompt_mode == |
| AutofillSaveProfilePromptMode::kUpdateProfile |
| ? IDS_IOS_SETTINGS_AUTOFILL_ACCOUNT_ADDRESS_FOOTER_TEXT |
| : IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_FOOTER); |
| EXPECT_NSEQ(GetFooterTextForModal(), |
| l10n_util::GetNSStringF(expected_text_id, kTestSyncingEmail)); |
| } |
| } |
| |
| TableViewTextEditItem* zip_item = |
| static_cast<TableViewTextEditItem*>(GetTableViewItem(0, 6)); |
| // Remove the zip field value. |
| zip_item.textFieldValue = @""; |
| [autofill_profile_edit_table_view_controller_ |
| tableViewItemDidChange:zip_item]; |
| |
| if (test_case.is_settings && test_case.account_profile) { |
| TableViewAttributedStringHeaderFooterItem* footer = |
| static_cast<TableViewAttributedStringHeaderFooterItem*>( |
| [[controller() tableViewModel] footerForSectionIndex:1]); |
| // Check that the error message has been updated. |
| EXPECT_NSEQ(GetErrorFooterString(2), footer.attributedString); |
| } |
| } |
| |
| // Tests the items in the view when the country value changes. |
| TEST_P(AutofillProfileEditTableViewControllerTest, |
| TestItemsOnCountrySelection) { |
| auto test_case = GetParam(); |
| TableViewTextEditItem* city_item = |
| static_cast<TableViewTextEditItem*>(GetTableViewItem(0, 4)); |
| // Remove the city field value. |
| city_item.textFieldValue = @""; |
| [autofill_profile_edit_table_view_controller_ |
| tableViewItemDidChange:city_item]; |
| |
| // Check the error message is shown. |
| if (test_case.is_settings && test_case.account_profile) { |
| TableViewAttributedStringHeaderFooterItem* footer = |
| static_cast<TableViewAttributedStringHeaderFooterItem*>( |
| [[controller() tableViewModel] footerForSectionIndex:1]); |
| // Check that the error message has been updated. |
| EXPECT_NSEQ(GetErrorFooterString(1), footer.attributedString); |
| } |
| |
| [autofill_profile_edit_table_view_controller_ |
| tableViewItemDidEndEditing:city_item]; |
| |
| CountryItem* countryItem = |
| [[CountryItem alloc] initWithType:kItemTypeEnumZero]; |
| countryItem.countryCode = @"DE"; |
| countryItem.text = @"Germany"; |
| |
| [autofill_profile_edit_mediator_ didSelectCountry:countryItem]; |
| |
| bool multiple_sections = (test_case.is_settings && test_case.account_profile); |
| EXPECT_EQ(NumberOfSections(), multiple_sections ? 2 : 1); |
| if (test_case.account_profile || |
| test_case.prompt_mode == AutofillSaveProfilePromptMode::kMigrateProfile) { |
| EXPECT_EQ(NumberOfItemsInSection(0), test_case.is_settings ? 10 : 12); |
| } else { |
| EXPECT_EQ(NumberOfItemsInSection(0), test_case.is_settings ? 10 : 11); |
| } |
| |
| // Check the error message persists. |
| if (test_case.is_settings && test_case.account_profile) { |
| TableViewAttributedStringHeaderFooterItem* footer = |
| static_cast<TableViewAttributedStringHeaderFooterItem*>( |
| [[controller() tableViewModel] footerForSectionIndex:1]); |
| // Check that the error message has been updated. |
| EXPECT_NSEQ(GetErrorFooterString(1), footer.attributedString); |
| } |
| } |
| |
| class AutofillProfileEditTableViewControllerTestWithDynamicFieldsEnabled |
| : public AutofillProfileEditTableViewControllerTest { |
| protected: |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature( |
| kAutofillDynamicallyLoadsFieldsForAddressInput); |
| |
| AutofillProfileEditTableViewControllerTest::SetUp(); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* No InstantiationName */, |
| AutofillProfileEditTableViewControllerTestWithDynamicFieldsEnabled, |
| testing::Values( |
| // Editing an account profile via settings. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/YES, |
| /*is_settings=*/YES}, |
| |
| // Editing a local profile via settings. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/NO, |
| /*is_settings=*/YES}, |
| |
| // Save Flow via Overlay UI: Editing an account profile. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/YES, |
| /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kNewProfile, /*account_profile=*/NO, |
| /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing an account profile after showing |
| // the update prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kUpdateProfile, |
| /*account_profile=*/YES, /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile after showing the |
| // update prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kUpdateProfile, |
| /*account_profile=*/NO, /*is_settings=*/NO}, |
| |
| // Save Flow via Overlay UI: Editing a local profile after showing the |
| // migration to account prompt. |
| AutofillProfileEditTableViewControllerTestCase{ |
| AutofillSaveProfilePromptMode::kMigrateProfile, |
| /*account_profile=*/NO, /*is_settings=*/NO})); |
| |
| // Test the sections and items in the section on initialisation as well as when |
| // country value is changed. |
| TEST_P(AutofillProfileEditTableViewControllerTestWithDynamicFieldsEnabled, |
| SectionsAndItems) { |
| auto test_case = GetParam(); |
| EXPECT_EQ(NumberOfSections(), |
| (test_case.is_settings && !test_case.account_profile) ? 3 : 4); |
| EXPECT_EQ(NumberOfItemsInSection(0), 2); |
| EXPECT_EQ(NumberOfItemsInSection(1), 5); |
| EXPECT_EQ(NumberOfItemsInSection(2), 2); |
| |
| if (test_case.account_profile) { |
| EXPECT_EQ(NumberOfItemsInSection(3), test_case.is_settings ? 0 : 2); |
| if (test_case.is_settings) { |
| NSString* expected_footer_text = l10n_util::GetNSStringF( |
| IDS_IOS_SETTINGS_AUTOFILL_ACCOUNT_ADDRESS_FOOTER_TEXT, |
| kTestSyncingEmail); |
| CheckSectionFooter(expected_footer_text, 3); |
| } |
| } else if (!test_case.is_settings) { |
| EXPECT_EQ( |
| NumberOfItemsInSection(3), |
| test_case.prompt_mode == AutofillSaveProfilePromptMode::kMigrateProfile |
| ? 2 |
| : 1); |
| } |
| |
| CountryItem* countryItem = |
| [[CountryItem alloc] initWithType:kItemTypeEnumZero]; |
| countryItem.countryCode = @"DE"; |
| countryItem.text = @"Germany"; |
| |
| [autofill_profile_edit_mediator_ didSelectCountry:countryItem]; |
| |
| EXPECT_EQ(NumberOfItemsInSection(1), 4); |
| } |