910e62b5创建于 1月15日历史提交
// Copyright 2025 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/app/profile/first_run_profile_agent.h"

#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/sync_preferences/features.h"
#import "components/sync_preferences/testing_pref_service_syncable.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/app/application_delegate/fake_startup_information.h"
#import "ios/chrome/app/profile/first_run_profile_agent+Testing.h"
#import "ios/chrome/app/profile/profile_init_stage.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/app/profile/profile_state_observer.h"
#import "ios/chrome/app/profile/profile_state_test_utils.h"
#import "ios/chrome/browser/first_run/ui_bundled/features.h"
#import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
#import "ios/chrome/browser/shared/coordinator/scene/test/stub_browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/guided_tour_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/synced_set_up/public/synced_set_up_metrics.h"
#import "ios/chrome/browser/welcome_back/model/features.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

// Tests the FirstRunProfileAgent.
class FirstRunProfileAgentTest : public PlatformTest {
 public:
  explicit FirstRunProfileAgentTest() {
    TestProfileIOS::Builder builder;
    profile_ = std::move(builder).Build();

    profile_state_ = [[ProfileState alloc] initWithAppState:nil];
    profile_state_.profile = profile_.get();

    profile_agent_ = [[FirstRunProfileAgent alloc] init];
    [profile_state_ addAgent:profile_agent_];
  }

  ~FirstRunProfileAgentTest() override {
    [scene_state_ shutdown];
    profile_state_.profile = nullptr;
  }

  // Initializes `browser_` using a configured `profile_` and `scene_state_`.
  void InitializeTestBrowser() {
    browser_ = std::make_unique<TestBrowser>(profile_.get(), scene_state_);
    scene_state_.browserProviderInterface.currentBrowserProvider.browser =
        browser_.get();
  }

  // Initializes `scene_state_` using a configured `profile_`.
  void InitializeActiveSceneState(bool is_off_the_record = false) {
    StubBrowserProviderInterface* browser_provider_interface =
        [[StubBrowserProviderInterface alloc] init];
    browser_provider_interface.currentBrowserProvider =
        is_off_the_record ? browser_provider_interface.incognitoBrowserProvider
                          : browser_provider_interface.mainBrowserProvider;

    scene_state_ = [[FakeSceneState alloc] initWithAppState:nil
                                                    profile:profile_.get()];
    scene_state_.activationLevel = SceneActivationLevelForegroundActive;
    scene_state_.browserProviderInterface = browser_provider_interface;
  }

 protected:
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  web::WebTaskEnvironment task_environment_;
  base::test::ScopedFeatureList enabled_feature_list_;
  std::unique_ptr<TestProfileIOS> profile_;
  FirstRunProfileAgent* profile_agent_;
  ProfileState* profile_state_;
  FakeSceneState* scene_state_;
  std::unique_ptr<TestBrowser> browser_;
};

// Validates that the correct metric is logged when the user rejects Guided Tour
// promo.
TEST_F(FirstRunProfileAgentTest, GuidedTourPromoMetrics) {
  enabled_feature_list_.InitAndEnableFeatureWithParameters(
      kBestOfAppFRE, {{kWelcomeBackParam, "4"}});
  base::HistogramTester tester;
  [profile_agent_ dismissGuidedTourPromo];
  tester.ExpectTotalCount("IOS.GuidedTour.Promo.DidAccept", 1);
}

// Validates that the correct metric is logged when a step in the Guided Tour
// finishes.
TEST_F(FirstRunProfileAgentTest, GuidedTourStepMetrics) {
  enabled_feature_list_.InitAndEnableFeatureWithParameters(
      kBestOfAppFRE, {{kWelcomeBackParam, "4"}});
  base::HistogramTester tester;
  [profile_agent_ nextTappedForStep:GuidedTourStep::kTabGridIncognito];
  tester.ExpectBucketCount("IOS.GuidedTour.DidFinishStep", 1, 1);
}

// Validates that the Synced Set Up flow does not trigger from an off-the-record
// profile.
TEST_F(FirstRunProfileAgentTest, SyncedSetUpDoesNotTriggerInIncognito) {
  enabled_feature_list_.InitAndEnableFeature(
      sync_preferences::features::kEnableCrossDevicePrefTracker);

  // Ensure that conditions are met for Synced Set Up to be shown on a
  // non-incognito profile.
  auto pref_service =
      std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
  pref_service->registry()->RegisterIntegerPref(
      prefs::kSyncedSetUpImpressionCount, 0);
  ASSERT_TRUE(pref_service->FindPreference(prefs::kSyncedSetUpImpressionCount));
  ASSERT_LT(pref_service->GetInteger(prefs::kSyncedSetUpImpressionCount),
            GetSyncedSetUpImpressionLimit());

  TestProfileIOS::Builder builder;
  builder.SetPrefService(std::move(pref_service));
  profile_ = std::move(builder).Build();

  InitializeActiveSceneState(/*is_off_the_record*/ true);
  ASSERT_NE(scene_state_.browserProviderInterface.currentBrowserProvider, nil);

  InitializeTestBrowser();

  // Configure the app state for the post-first run flow.
  FakeStartupInformation* startup_information =
      [[FakeStartupInformation alloc] init];
  startup_information.isFirstRun = YES;

  AppState* app_state =
      [[AppState alloc] initWithStartupInformation:startup_information];

  [profile_state_ removeAgent:profile_agent_];
  profile_state_ = [profile_state_ initWithAppState:app_state];
  [profile_state_ sceneStateConnected:scene_state_];
  SetProfileStateInitStage(profile_state_, ProfileInitStage::kFinal);

  // Assert that whether the profile is off-the-record will be checked by the
  // profile agent.
  ASSERT_TRUE(profile_state_.appState.startupInformation.isFirstRun);
  ASSERT_EQ(profile_state_.initStage, ProfileInitStage::kFinal);
  ASSERT_FALSE(profile_state_.currentUIBlocker);
  ASSERT_TRUE(profile_state_.foregroundActiveScene);

  // Partial mock profile agent that prevents the profile agent from executing
  // other post-first run actions.
  id mock_profile_agent = OCMPartialMock(profile_agent_);
  OCMStub([mock_profile_agent showFirstRunUI]);
  OCMStub([mock_profile_agent performNextPostFirstRunAction]);

  [profile_state_ addAgent:mock_profile_agent];

  // Verify that no Synced Set Up triggers are logged after attempting to show
  // Synced Set Up on an off-the-record profile.
  base::HistogramTester histogram_tester;
  [mock_profile_agent profileState:profile_state_
        firstSceneHasInitializedUI:scene_state_];
  [mock_profile_agent showSyncedSetUp];
  histogram_tester.ExpectBucketCount(
      "IOS.SyncedSetUp.TriggerSource",
      static_cast<int>(SyncedSetUpTriggerSource::kPostFirstRun), 0);
  histogram_tester.ExpectTotalCount("IOS.SyncedSetUp.TriggerSource", 0);
}