chromium/ios/chrome/browser/ntp/model/set_up_list_unittest.mm

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

#import "ios/chrome/browser/ntp/model/set_up_list.h"

#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/gtest_util.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/password_manager/core/browser/password_manager_util.h"
#import "components/prefs/scoped_user_pref_update.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/sync/base/pref_names.h"
#import "components/sync_preferences/testing_pref_service_syncable.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/default_browser/model/utils_test_support.h"
#import "ios/chrome/browser/ntp/model/set_up_list_delegate.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item_type.h"
#import "ios/chrome/browser/ntp/model/set_up_list_prefs.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/push_notification/model/constants.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_ios.h"
#import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_manager_ios.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/signin/model/fake_system_identity_manager.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"

using set_up_list_prefs::SetUpListItemState;

// Test fixture for testing the SetUpList class.
class SetUpListTest : public PlatformTest {
 public:
  SetUpListTest() {
    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetDefaultFactory());
    browser_state_ = profile_manager_.AddProfileWithBuilder(std::move(builder));
    prefs_ = GetBrowserState()->GetPrefs();
    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
        GetBrowserState(),
        std::make_unique<FakeAuthenticationServiceDelegate>());
    auth_service_ =
        AuthenticationServiceFactory::GetForBrowserState(GetBrowserState());
    content_notification_feature_enabled_ = false;
  }

  ~SetUpListTest() override { [set_up_list_ disconnect]; }

  // Get the test BrowserState.
  ChromeBrowserState* GetBrowserState() { return browser_state_.get(); }

  // Get the LocalState prefs.
  PrefService* GetLocalState() {
    return GetApplicationContext()->GetLocalState();
  }

  // Builds a new instance of SetUpList.
  void BuildSetUpList() {
    [set_up_list_ disconnect];
    set_up_list_ =
        [SetUpList buildFromPrefs:prefs_
                            localState:GetLocalState()
                           syncService:SyncServiceFactory::GetForBrowserState(
                                           GetBrowserState())
                 authenticationService:auth_service_
            contentNotificationEnabled:content_notification_feature_enabled_];
  }

  // Fakes a sign-in with a fake identity.
  void SignInFakeIdentity() {
    FakeSystemIdentity* identity = [FakeSystemIdentity fakeIdentity1];
    FakeSystemIdentityManager* system_identity_manager =
        FakeSystemIdentityManager::FromSystemIdentityManager(
            GetApplicationContext()->GetSystemIdentityManager());
    system_identity_manager->AddIdentity(identity);
    auth_service_->SignIn(identity,
                          signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);
    auth_service_->GrantSyncConsent(
        identity, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN);

    profile_manager_.GetProfileAttributesStorage()
        ->UpdateAttributesForProfileWithName(
            browser_state_->GetBrowserStateName(),
            base::BindOnce(
                [](id<SystemIdentity> identity, ProfileAttributesIOS attr) {
                  attr.SetAuthenticationInfo(
                      base::SysNSStringToUTF8(identity.gaiaID),
                      base::SysNSStringToUTF8(identity.userEmail));
                  return attr;
                },
                identity));
  }

  // Ensures that Chrome is considered as default browser.
  void SetTrueChromeLikelyDefaultBrowser() { LogOpenHTTPURLFromExternalURL(); }

  // Ensures that Chrome is not considered as default browser.
  void SetFalseChromeLikelyDefaultBrowser() { ClearDefaultBrowserPromoData(); }

  // Fakes enabling or disabling the credential provider.
  void FakeEnableCredentialProvider(bool enable) {
    password_manager_util::SetCredentialProviderEnabledOnStartup(prefs_,
                                                                 enable);
  }

  // Enables/disables tips notifications.
  void SetTipsNotificationsEnabled(bool enable) {
    ScopedDictPrefUpdate update(GetLocalState(),
                                prefs::kAppLevelPushNotificationPermissions);
    update->Set(kTipsNotificationKey, enable);
  }

  // Enables/disables content notifications.
  void SetContentNotificationsEnabled(bool enable) {
    ScopedDictPrefUpdate update(prefs_,
                                prefs::kFeaturePushNotificationPermissions);
    update->Set(kContentNotificationKey, enable);
  }

  // Returns the item with the given `type`. Returns nil if not found.
  SetUpListItem* FindItem(SetUpListItemType type) {
    for (SetUpListItem* item in set_up_list_.items) {
      if (item.type == type) {
        return item;
      }
    }
    return nil;
  }

  // Expects the built SetUpList to include an item with the given `type` and
  // expects it to have the given `complete` status.
  void ExpectListToInclude(SetUpListItemType type, BOOL complete) {
    SetUpListItem* item = FindItem(type);
    EXPECT_TRUE(item);
    EXPECT_EQ(item.complete, complete);
  }

  // Expects the built SetUpList to not include an item with the given `type`.
  void ExpectListToNotInclude(SetUpListItemType type) {
    SetUpListItem* item = FindItem(type);
    EXPECT_EQ(item, nil);
  }

  // Gets the state of the item with the given `type` from prefs.
  SetUpListItemState GetItemState(SetUpListItemType type) {
    return set_up_list_prefs::GetItemState(GetLocalState(), type);
  }

  // Sets the state of the item with the given `type` from prefs.
  void SetItemState(SetUpListItemType type, SetUpListItemState state) {
    set_up_list_prefs::SetItemState(GetLocalState(), type, state);
  }

  NSUInteger GetItemIndex(SetUpListItemType type) {
    for (NSUInteger i = 0; i < [set_up_list_.items count]; i++) {
      if (set_up_list_.items[i].type == type) {
        return i;
      }
    }
    return -1;
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  base::test::ScopedFeatureList feature_list_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  TestProfileManagerIOS profile_manager_;
  raw_ptr<ChromeBrowserState> browser_state_;
  raw_ptr<PrefService> prefs_;
  raw_ptr<AuthenticationService> auth_service_;
  SetUpList* set_up_list_;
  bool content_notification_feature_enabled_;
};

// Tests the SignInSync item is hidden if sync is disabled by policy.
TEST_F(SetUpListTest, NoSignInSyncIfSyncDisabledByPolicy) {
  prefs_->SetBoolean(syncer::prefs::internal::kSyncManaged, true);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kSignInSync);

  prefs_->ClearPref(syncer::prefs::internal::kSyncManaged);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kSignInSync, NO);
}

// Tests the SignInSync item is hidden if sign-in is disabled by policy.
TEST_F(SetUpListTest, NoSignInSyncItemIfSigninDisabledByPolicy) {
  // Set sign-in disabled by policy.
  GetLocalState()->SetInteger(prefs::kBrowserSigninPolicy,
                              static_cast<int>(BrowserSigninMode::kDisabled));
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kSignInSync);
  // Re-enable signin policy.
  GetLocalState()->SetInteger(prefs::kBrowserSigninPolicy,
                              static_cast<int>(BrowserSigninMode::kEnabled));
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kSignInSync, NO);
}

// Tests that the SetUpList shows or hides the SignInSync item depending on
// whether the user is currently signed-in.
TEST_F(SetUpListTest, SignInSyncReactsToAccountChanges) {
  SignInFakeIdentity();
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kSignInSync, YES);
  EXPECT_EQ(GetItemState(SetUpListItemType::kSignInSync),
            SetUpListItemState::kCompleteInList);

  SetItemState(SetUpListItemType::kSignInSync,
               SetUpListItemState::kCompleteNotInList);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kSignInSync);
  EXPECT_EQ(GetItemState(SetUpListItemType::kSignInSync),
            SetUpListItemState::kCompleteNotInList);
}

// Tests that the SetUpList uses the correct criteria when including the
// DefaultBrowser item.
TEST_F(SetUpListTest, BuildListWithDefaultBrowser) {
  SetFalseChromeLikelyDefaultBrowser();
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kDefaultBrowser, NO);

  SetTrueChromeLikelyDefaultBrowser();
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kDefaultBrowser, YES);
  EXPECT_EQ(GetItemState(SetUpListItemType::kDefaultBrowser),
            SetUpListItemState::kCompleteInList);

  SetItemState(SetUpListItemType::kDefaultBrowser,
               SetUpListItemState::kCompleteNotInList);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kDefaultBrowser);
  EXPECT_EQ(GetItemState(SetUpListItemType::kDefaultBrowser),
            SetUpListItemState::kCompleteNotInList);
}

// Tests that the SetUpList uses the correct criteria when including the
// Autofill item.
TEST_F(SetUpListTest, BuildListWithAutofill) {
  FakeEnableCredentialProvider(false);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kAutofill, NO);

  FakeEnableCredentialProvider(true);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kAutofill, YES);
  EXPECT_EQ(GetItemState(SetUpListItemType::kAutofill),
            SetUpListItemState::kCompleteInList);

  SetItemState(SetUpListItemType::kAutofill,
               SetUpListItemState::kCompleteNotInList);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kAutofill);
  EXPECT_EQ(GetItemState(SetUpListItemType::kAutofill),
            SetUpListItemState::kCompleteNotInList);
}

// Tests that the SetUpList uses the correct criteria when including the
// Notifications item and tips notification is enabled.
TEST_F(SetUpListTest, BuildListWithNotifications_Tips) {
  feature_list_.InitAndEnableFeature(kIOSTipsNotifications);
  SetTipsNotificationsEnabled(false);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kNotifications, NO);

  SetTipsNotificationsEnabled(true);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kNotifications, YES);
  EXPECT_EQ(GetItemState(SetUpListItemType::kNotifications),
            SetUpListItemState::kCompleteInList);

  SetItemState(SetUpListItemType::kNotifications,
               SetUpListItemState::kCompleteNotInList);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kNotifications);
  EXPECT_EQ(GetItemState(SetUpListItemType::kNotifications),
            SetUpListItemState::kCompleteNotInList);
}

// Tests that the SetUpList uses the correct criteria when including the
// Notifications item and content notifications is enabled.
TEST_F(SetUpListTest, BuildListWithNotifications_Content) {
  content_notification_feature_enabled_ = YES;

  SetContentNotificationsEnabled(false);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kNotifications, NO);

  SetContentNotificationsEnabled(true);
  BuildSetUpList();
  ExpectListToInclude(SetUpListItemType::kNotifications, YES);
  EXPECT_EQ(GetItemState(SetUpListItemType::kNotifications),
            SetUpListItemState::kCompleteInList);

  SetItemState(SetUpListItemType::kNotifications,
               SetUpListItemState::kCompleteNotInList);
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kNotifications);
  EXPECT_EQ(GetItemState(SetUpListItemType::kNotifications),
            SetUpListItemState::kCompleteNotInList);
}

// Tests that the SetUpList uses the correct criteria when including the
// Follow item.
TEST_F(SetUpListTest, BuildListWithFollow) {
  BuildSetUpList();
  ExpectListToNotInclude(SetUpListItemType::kFollow);
}

// Tests that SetUpList observes local state changes, updates the item, and
// calls the delegate.
TEST_F(SetUpListTest, ObservesPrefs) {
  BuildSetUpList();
  id delegate = [OCMockObject mockForProtocol:@protocol(SetUpListDelegate)];
  set_up_list_.delegate = delegate;
  SetUpListItem* item = FindItem(SetUpListItemType::kSignInSync);
  EXPECT_FALSE(item.complete);
  OCMExpect([delegate setUpListItemDidComplete:item allItemsCompleted:NO]);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kSignInSync);
  EXPECT_TRUE(item.complete);
  [delegate verify];
}

// Tests that `allItemsComplete` correctly returns whether all items are
// complete.
TEST_F(SetUpListTest, AllItemsComplete) {
  base::HistogramTester histogram_tester;
  feature_list_.InitAndEnableFeature(kIOSTipsNotifications);
  BuildSetUpList();
  EXPECT_FALSE([set_up_list_ allItemsComplete]);
  histogram_tester.ExpectBucketCount("IOS.SetUpList.AllItemsCompleted", true,
                                     0);

  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kSignInSync);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kDefaultBrowser);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kAutofill);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kNotifications);

  EXPECT_TRUE([set_up_list_ allItemsComplete]);
  histogram_tester.ExpectBucketCount("IOS.SetUpList.AllItemsCompleted", true,
                                     1);
}

TEST_F(SetUpListTest, RecordsAllItemsCompleteOnce) {
  base::HistogramTester histogram_tester;
  feature_list_.InitAndEnableFeature(kIOSTipsNotifications);
  BuildSetUpList();
  histogram_tester.ExpectBucketCount("IOS.SetUpList.AllItemsCompleted", true,
                                     0);

  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kSignInSync);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kDefaultBrowser);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kAutofill);
  set_up_list_prefs::MarkItemComplete(GetLocalState(),
                                      SetUpListItemType::kNotifications);
  histogram_tester.ExpectBucketCount("IOS.SetUpList.AllItemsCompleted", true,
                                     1);

  // Ensure that this metric is not double-counted, when rebuilding the list.
  BuildSetUpList();
  histogram_tester.ExpectBucketCount("IOS.SetUpList.AllItemsCompleted", true,
                                     1);
}

// Tests that the Set Up List can be disabled.
TEST_F(SetUpListTest, Disable) {
  EXPECT_FALSE(set_up_list_prefs::IsSetUpListDisabled(GetLocalState()));
  set_up_list_prefs::DisableSetUpList(GetLocalState());
  EXPECT_TRUE(set_up_list_prefs::IsSetUpListDisabled(GetLocalState()));

  BuildSetUpList();
  EXPECT_EQ(set_up_list_, nil);
}

// Tests that the Set Up List item order is correct with kMagicStack enabled.
TEST_F(SetUpListTest, MagicStackItemOrder) {
  feature_list_.InitWithFeatures({kIOSTipsNotifications}, {});
  BuildSetUpList();

  EXPECT_EQ(GetItemIndex(SetUpListItemType::kDefaultBrowser), 0u);
  EXPECT_EQ(GetItemIndex(SetUpListItemType::kAutofill), 1u);
  EXPECT_EQ(GetItemIndex(SetUpListItemType::kNotifications), 2u);
  EXPECT_EQ(GetItemIndex(SetUpListItemType::kSignInSync), 3u);
}