chromium/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator_unittest.mm

// Copyright 2022 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/ui_bundled/new_tab_page_mediator.h"

#import <memory>
#import <string_view>

#import "base/memory/raw_ptr.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/feed/core/v2/public/common_enums.h"
#import "components/search_engines/search_engines_switches.h"
#import "components/search_engines/template_url.h"
#import "components/search_engines/template_url_data.h"
#import "components/search_engines/template_url_service.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/sync/test/test_sync_service.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_item.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.h"
#import "ios/chrome/browser/ui/content_suggestions/user_account_image_update_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_control_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/logo_vendor.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_constants.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_consumer.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_consumer.h"
#import "ios/chrome/browser/ui/toolbar/test/toolbar_test_navigation_manager.h"
#import "ios/chrome/browser/url_loading/model/fake_url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_notifier_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/fakes/fake_web_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"

using feed::FeedUserActionType;

// Expects a URL to start with a prefix.
#define EXPECT_URL_PREFIX(url, prefix) \
  EXPECT_STREQ(url.spec().substr(0, strlen(prefix)).c_str(), prefix);

// Expects the URL loader to have loaded a URL that has the given `prefix`.
#define EXPECT_URL_LOAD(prefix) \
  EXPECT_URL_PREFIX(url_loader_->last_params.web_params.url, prefix);

class NewTabPageMediatorTest : public PlatformTest {
 public:
  NewTabPageMediatorTest() {
    TestChromeBrowserState::Builder test_cbs_builder;
    test_cbs_builder.AddTestingFactory(
        ios::TemplateURLServiceFactory::GetInstance(),
        ios::TemplateURLServiceFactory::GetDefaultFactory());
    test_cbs_builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetDefaultFactory());
    chrome_browser_state_ = std::move(test_cbs_builder).Build();
    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
        chrome_browser_state_.get(),
        std::make_unique<FakeAuthenticationServiceDelegate>());
    browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());

    std::unique_ptr<ToolbarTestNavigationManager> navigation_manager =
        std::make_unique<ToolbarTestNavigationManager>();
    navigation_manager_ = navigation_manager.get();
    initial_web_state_ = CreateWebStateWithURL(GURL("chrome://newtab"), 0.0);
    logo_vendor_ = OCMProtocolMock(@protocol(LogoVendor));

    UrlLoadingNotifierBrowserAgent::CreateForBrowser(browser_.get());
    FakeUrlLoadingBrowserAgent::InjectForBrowser(browser_.get());
    url_loader_ = FakeUrlLoadingBrowserAgent::FromUrlLoadingBrowserAgent(
        UrlLoadingBrowserAgent::FromBrowser(browser_.get()));

    auth_service_ = static_cast<AuthenticationService*>(
        AuthenticationServiceFactory::GetInstance()->GetForBrowserState(
            chrome_browser_state_.get()));
    identity_manager_ =
        IdentityManagerFactory::GetForBrowserState(chrome_browser_state_.get());
    ChromeAccountManagerService* account_manager_service =
        ChromeAccountManagerServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());
    image_updater_ = OCMProtocolMock(@protocol(UserAccountImageUpdateDelegate));
    bool is_incognito = chrome_browser_state_.get()->IsOffTheRecord();
    DiscoverFeedService* discover_feed_service =
        DiscoverFeedServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());
    PrefService* prefs = chrome_browser_state_->GetPrefs();
    mediator_ = [[NewTabPageMediator alloc]
        initWithTemplateURLService:ios::TemplateURLServiceFactory::
                                       GetForBrowserState(
                                           chrome_browser_state_.get())
                         URLLoader:url_loader_
                       authService:auth_service_
                   identityManager:identity_manager_
             accountManagerService:account_manager_service
          identityDiscImageUpdater:image_updater_
                       isIncognito:is_incognito
               discoverFeedService:discover_feed_service
                       prefService:prefs
                       syncService:&test_sync_service_
                        isSafeMode:NO];
    header_consumer_ = OCMProtocolMock(@protocol(NewTabPageHeaderConsumer));
    mediator_.headerConsumer = header_consumer_;
    feed_metrics_recorder_ =
        [[FeedMetricsRecorder alloc] initWithPrefService:prefs];
    mediator_.feedMetricsRecorder = feed_metrics_recorder_;
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  // Explicitly disconnect the mediator.
  ~NewTabPageMediatorTest() override { [mediator_ shutdown]; }

  // Creates a FakeWebState and simulates that it is loaded with a given `url`.
  std::unique_ptr<web::WebState> CreateWebStateWithURL(
      const GURL& url,
      CGFloat scroll_position = 0.0) {
    auto web_state = std::make_unique<web::FakeWebState>();
    web_state->SetBrowserState(chrome_browser_state_.get());
    NewTabPageTabHelper::CreateForWebState(web_state.get());
    web_state->SetVisibleURL(url);
    // Force the DidStopLoading callback.
    web_state->SetLoading(true);
    web_state->SetLoading(false);
    return std::move(web_state);
  }

  id SetupNTPConsumerMock() {
    id ntp_consumer = OCMProtocolMock(@protocol(NewTabPageConsumer));
    [[[ntp_consumer expect] andReturnValue:[NSNumber numberWithDouble:0.0]]
        heightAboveFeed];
    mediator_.consumer = ntp_consumer;
    return ntp_consumer;
  }

  void SetCustomSearchEngine() {
    TemplateURLService* template_url_service =
        ios::TemplateURLServiceFactory::GetForBrowserState(
            chrome_browser_state_.get());
    // A custom search engine will have a `prepopulate_id` of 0.
    const int kCustomSearchEnginePrepopulateId = 0;
    TemplateURLData template_url_data;
    template_url_data.prepopulate_id = kCustomSearchEnginePrepopulateId;
    template_url_data.SetURL("https://www.example.com/?q={searchTerms}");
    template_url_service->SetUserSelectedDefaultSearchProvider(
        template_url_service->Add(
            std::make_unique<TemplateURL>(template_url_data)));
  }

  void OverrideSearchEngineChoiceCountry(std::string_view country) {
    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        switches::kSearchEngineChoiceCountry, country);
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
  std::unique_ptr<Browser> browser_;
  std::unique_ptr<web::WebState> initial_web_state_;
  id header_consumer_;
  id image_updater_;
  id logo_vendor_;
  FeedMetricsRecorder* feed_metrics_recorder_;
  NewTabPageMediator* mediator_;
  raw_ptr<ToolbarTestNavigationManager> navigation_manager_;
  raw_ptr<FakeUrlLoadingBrowserAgent> url_loader_;
  raw_ptr<AuthenticationService> auth_service_;
  raw_ptr<signin::IdentityManager> identity_manager_;
  syncer::TestSyncService test_sync_service_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests that the consumer has the right value set up.
TEST_F(NewTabPageMediatorTest, TestConsumerSetup) {
  // Setup.
  OCMExpect([header_consumer_ setLogoIsShowing:YES]);

  // Action.
  [mediator_ setUp];

  // Tests.
  EXPECT_OCMOCK_VERIFY(header_consumer_);
}

// Tests that the FeedManagementNavigationDelegate methods load URLs and
// record metrics.
TEST_F(NewTabPageMediatorTest, TestFeedManagementNavigationDelegate) {
  [mediator_ handleNavigateToActivity];
  EXPECT_URL_LOAD("https://myactivity.google.com/myactivity");
  histogram_tester_->ExpectUniqueSample(
      kDiscoverFeedUserActionHistogram,
      FeedUserActionType::kTappedManageActivity, 1);

  histogram_tester_.reset(new base::HistogramTester());
  [mediator_ handleNavigateToFollowing];
  EXPECT_URL_LOAD("https://google.com/preferences/interests");
  histogram_tester_->ExpectUniqueSample(
      kDiscoverFeedUserActionHistogram,
      FeedUserActionType::kTappedManageFollowing, 1);

  histogram_tester_.reset(new base::HistogramTester());
  [mediator_ handleNavigateToHidden];
  EXPECT_URL_LOAD("https://google.com/preferences/interests/hidden");
  histogram_tester_->ExpectUniqueSample(kDiscoverFeedUserActionHistogram,
                                        FeedUserActionType::kTappedManageHidden,
                                        1);

  histogram_tester_.reset(new base::HistogramTester());
  GURL followed_url("https://example.org");
  [mediator_ handleNavigateToFollowedURL:followed_url];
  EXPECT_URL_LOAD(followed_url.spec().c_str());
  // TODO(crbug.com/40227407): Add metrics.
}

// Tests that the handleFeedLearnMoreTapped loads the correct URL and records
// metrics.
TEST_F(NewTabPageMediatorTest, TestHandleFeedLearnMoreTapped) {
  [mediator_ handleFeedLearnMoreTapped];
  EXPECT_URL_LOAD("https://support.google.com/chrome/"
                  "?p=new_tab&co=GENIE.Platform%3DiOS&oco=1");
  histogram_tester_->ExpectUniqueSample(kDiscoverFeedUserActionHistogram,
                                        FeedUserActionType::kTappedLearnMore,
                                        1);
}

// Tests that the feed will be hidden when a non-Google search engine is chosen,
// but only in EEA countries.
TEST_F(NewTabPageMediatorTest, TestHideFeedWithSearchChoiceTargeted) {
  // Test it with the default search engine, with country set to France.
  OverrideSearchEngineChoiceCountry("FR");
  [mediator_ setUp];
  EXPECT_TRUE(mediator_.feedHeaderVisible);

  // Set up expectation for custom search engine, country set to France.
  id feed_control_delegate = OCMProtocolMock(@protocol(FeedControlDelegate));
  OCMExpect([feed_control_delegate setFeedAndHeaderVisibility:NO]);
  mediator_.feedControlDelegate = feed_control_delegate;

  // Test setting a custom search engine, country still set to France.
  SetCustomSearchEngine();
  EXPECT_FALSE(mediator_.feedHeaderVisible);
  EXPECT_OCMOCK_VERIFY(feed_control_delegate);

  // Set up expectation for custom search engine, with country set to US.
  feed_control_delegate = OCMProtocolMock(@protocol(FeedControlDelegate));
  OCMExpect([feed_control_delegate setFeedAndHeaderVisibility:YES]);
  mediator_.feedControlDelegate = feed_control_delegate;

  // Test with custom search engine, with country set to US.
  OverrideSearchEngineChoiceCountry("US");
  SetCustomSearchEngine();
  EXPECT_TRUE(mediator_.feedHeaderVisible);
  EXPECT_OCMOCK_VERIFY(feed_control_delegate);
}