chromium/ios/chrome/browser/ui/menu/browser_action_factory_unittest.mm

// Copyright 2021 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/menu/browser_action_factory.h"

#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/task_environment.h"
#import "components/policy/core/common/policy_pref_names.h"
#import "components/sync_preferences/testing_pref_service_syncable.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/sessions/model/test_session_service.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/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/load_query_commands.h"
#import "ios/chrome/browser/shared/public/commands/qr_scanner_commands.h"
#import "ios/chrome/browser/shared/public/commands/save_to_photos_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/ui/menu/menu_action_type.h"
#import "ios/chrome/browser/ui/menu/menu_histograms.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/browser/window_activities/model/window_activity_helpers.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "ui/base/test/ios/ui_image_test_utils.h"
#import "url/gurl.h"

namespace {
const MenuScenarioHistogram kTestMenuScenario =
    kMenuScenarioHistogramHistoryEntry;
}  // namespace

// Test fixture for the BrowserActionFactory.
class BrowserActionFactoryTest : public PlatformTest {
 protected:
  BrowserActionFactoryTest() : test_title_(@"SomeTitle") {}

  void SetUp() override {
    TestChromeBrowserState::Builder test_cbs_builder;
    chrome_browser_state_ = std::move(test_cbs_builder).Build();

    test_browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());

    mock_application_commands_handler_ =
        OCMStrictProtocolMock(@protocol(ApplicationCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_application_commands_handler_
                     forProtocol:@protocol(ApplicationCommands)];

    mock_settings_commands_handler_ =
        OCMStrictProtocolMock(@protocol(SettingsCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_settings_commands_handler_
                     forProtocol:@protocol(SettingsCommands)];

    mock_browser_coordinator_commands_handler_ =
        OCMStrictProtocolMock(@protocol(BrowserCoordinatorCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_browser_coordinator_commands_handler_
                     forProtocol:@protocol(BrowserCoordinatorCommands)];

    mock_qr_scanner_commands_handler_ =
        OCMStrictProtocolMock(@protocol(QRScannerCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_qr_scanner_commands_handler_
                     forProtocol:@protocol(QRScannerCommands)];

    mock_load_query_commands_handler_ =
        OCMStrictProtocolMock(@protocol(LoadQueryCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_load_query_commands_handler_
                     forProtocol:@protocol(LoadQueryCommands)];

    mock_save_to_photos_commands_handler_ =
        OCMStrictProtocolMock(@protocol(SaveToPhotosCommands));
    [test_browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_save_to_photos_commands_handler_
                     forProtocol:@protocol(SaveToPhotosCommands)];
  }

  base::test::ScopedFeatureList feature_list_;
  base::test::TaskEnvironment task_environment_;
  base::HistogramTester histogram_tester_;
  NSString* test_title_;
  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
  std::unique_ptr<TestBrowser> test_browser_;
  id mock_application_commands_handler_;
  id mock_settings_commands_handler_;
  id mock_browser_coordinator_commands_handler_;
  id mock_qr_scanner_commands_handler_;
  id mock_load_query_commands_handler_;
  id mock_save_to_photos_commands_handler_;
};

// Tests that the Open in New Tab actions have the right titles and images.
TEST_F(BrowserActionFactoryTest, OpenInNewTabAction_URL) {
  GURL testURL = GURL("https://example.com");

  // Using an action factory with a browser should return expected action.
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      DefaultSymbolWithPointSize(kNewTabActionSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);

  UIAction* actionWithURL = [factory actionToOpenInNewTabWithURL:testURL
                                                      completion:nil];
  EXPECT_TRUE([expectedTitle isEqualToString:actionWithURL.title]);
  EXPECT_EQ(expectedImage, actionWithURL.image);

  UIAction* actionWithBlock = [factory actionToOpenInNewTabWithBlock:nil];
  EXPECT_TRUE([expectedTitle isEqualToString:actionWithBlock.title]);
  EXPECT_EQ(expectedImage, actionWithBlock.image);
}

// Tests that the Open in New Incognito Tab actions have the right titles
// and images.
TEST_F(BrowserActionFactoryTest, OpenInNewIncognitoTabAction_URL) {
  GURL testURL = GURL("https://example.com");

  // Using an action factory with a browser should return expected action.
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      CustomSymbolWithPointSize(kIncognitoSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE);

  UIAction* actionWithURL =
      [factory actionToOpenInNewIncognitoTabWithURL:testURL completion:nil];
  EXPECT_TRUE([expectedTitle isEqualToString:actionWithURL.title]);
  EXPECT_EQ(expectedImage, actionWithURL.image);

  UIAction* actionWithBlock =
      [factory actionToOpenInNewIncognitoTabWithBlock:nil];
  EXPECT_TRUE([expectedTitle isEqualToString:actionWithBlock.title]);
  EXPECT_EQ(expectedImage, actionWithBlock.image);
}

// Tests that the Open in New Window action has the right title and image.
TEST_F(BrowserActionFactoryTest, OpenInNewWindowAction) {
  GURL testURL = GURL("https://example.com");

  // Using an action factory with a browser should return expected action.
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage = DefaultSymbolWithPointSize(kNewWindowActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW);

  // Test URL variant
  UIAction* action =
      [factory actionToOpenInNewWindowWithURL:testURL
                               activityOrigin:WindowActivityToolsOrigin];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);

  // Test user activity variant
  action = [factory
      actionToOpenInNewWindowWithActivity:ActivityToLoadURL(
                                              WindowActivityToolsOrigin,
                                              testURL)];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the open image action has the right title and image.
TEST_F(BrowserActionFactoryTest, OpenImageAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  GURL testURL = GURL("https://example.com/logo.png");

  UIImage* expectedImage = DefaultSymbolWithPointSize(kOpenImageActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);

  UIAction* action = [factory actionOpenImageWithURL:testURL
                                          completion:^{
                                          }];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the open image in new tab action has the right title and image.
TEST_F(BrowserActionFactoryTest, OpenImageInNewTabAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  GURL testURL = GURL("https://example.com/logo.png");
  UrlLoadParams testParams = UrlLoadParams::InNewTab(testURL);

  UIImage* expectedImage =
      CustomSymbolWithPointSize(kPhotoBadgePlusSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);

  UIAction* action =
      [factory actionOpenImageInNewTabWithUrlLoadParams:testParams
                                             completion:^{
                                             }];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the hide preview action has the right title and image.
TEST_F(BrowserActionFactoryTest, OpenNewTabAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      DefaultSymbolWithPointSize(kNewTabActionSymbol, kSymbolActionPointSize);
  NSString* expectedTitle = l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_TAB);

  UIAction* action = [factory actionToOpenNewTab];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
  EXPECT_EQ(0U, action.attributes);

  chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
      policy::policy_prefs::kIncognitoModeAvailability,
      std::make_unique<base::Value>(
          static_cast<int>(IncognitoModePrefs::kForced)));

  UIAction* managed_action = [factory actionToOpenNewTab];
  EXPECT_EQ(UIMenuElementAttributesDisabled, managed_action.attributes);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, OpenNewIncognitoTabAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      CustomSymbolWithPointSize(kIncognitoSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB);

  UIAction* action = [factory actionToOpenNewIncognitoTab];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
  EXPECT_EQ(0U, action.attributes);

  chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
      policy::policy_prefs::kIncognitoModeAvailability,
      std::make_unique<base::Value>(
          static_cast<int>(IncognitoModePrefs::kDisabled)));

  UIAction* managed_action = [factory actionToOpenNewIncognitoTab];
  EXPECT_EQ(UIMenuElementAttributesDisabled, managed_action.attributes);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, CloseCurrentTabAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      DefaultSymbolWithPointSize(kXMarkSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_CLOSE_TAB);

  UIAction* action = [factory actionToCloseCurrentTab];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
  EXPECT_EQ(UIMenuElementAttributesDestructive, action.attributes);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, ShowQRScannerAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage = DefaultSymbolWithPointSize(kQRCodeFinderActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_QR_SCANNER);

  UIAction* action = [factory actionToShowQRScanner];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, StartVoiceSearchAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      DefaultSymbolWithPointSize(kMicrophoneSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_VOICE_SEARCH);

  UIAction* action = [factory actionToStartVoiceSearch];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the action has the right title, image and attributes.
TEST_F(BrowserActionFactoryTest, StartNewSearchAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      DefaultSymbolWithPointSize(kSearchSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_SEARCH);

  UIAction* action = [factory actionToStartNewSearch];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
  EXPECT_EQ(0U, action.attributes);

  chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
      policy::policy_prefs::kIncognitoModeAvailability,
      std::make_unique<base::Value>(
          static_cast<int>(IncognitoModePrefs::kForced)));

  UIAction* managed_action = [factory actionToStartNewSearch];
  EXPECT_EQ(UIMenuElementAttributesDisabled, managed_action.attributes);
}

// Tests that the action has the right title, image and attributes.
TEST_F(BrowserActionFactoryTest, NewIncognitoSearchAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage =
      CustomSymbolWithPointSize(kIncognitoSymbol, kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_SEARCH);

  UIAction* action = [factory actionToStartNewIncognitoSearch];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
  EXPECT_EQ(0U, action.attributes);

  chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
      policy::policy_prefs::kIncognitoModeAvailability,
      std::make_unique<base::Value>(
          static_cast<int>(IncognitoModePrefs::kDisabled)));

  UIAction* managed_action = [factory actionToStartNewIncognitoSearch];
  EXPECT_EQ(UIMenuElementAttributesDisabled, managed_action.attributes);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, SearchCopiedImageAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage = DefaultSymbolWithPointSize(kClipboardActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE);

  UIAction* action = [factory actionToSearchCopiedImage];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, SearchCopiedURLAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage = DefaultSymbolWithPointSize(kClipboardActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_VISIT_COPIED_LINK);

  UIAction* action = [factory actionToSearchCopiedURL];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, SearchCopiedTextAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

  UIImage* expectedImage = DefaultSymbolWithPointSize(kClipboardActionSymbol,
                                                      kSymbolActionPointSize);
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_SEARCH_COPIED_TEXT);

  UIAction* action = [factory actionToSearchCopiedText];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}

// Tests that the action has the right title and image.
TEST_F(BrowserActionFactoryTest, SaveImageInGooglePhotosAction) {
  BrowserActionFactory* factory =
      [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                           scenario:kTestMenuScenario];

#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
  UIImage* expectedImage =
      CustomSymbolWithPointSize(kGooglePhotosSymbol, kSymbolActionPointSize);
#else
  UIImage* expectedImage = DefaultSymbolWithPointSize(kSaveImageActionSymbol,
                                                      kSymbolActionPointSize);
#endif
  NSString* expectedTitle =
      l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_SAVE_IMAGE_TO_PHOTOS);

  GURL fakeImageURL("https://example.com/image.png");
  web::Referrer fakeImageReferrer;
  std::unique_ptr<web::WebState> fakeWebState =
      std::make_unique<web::FakeWebState>();
  UIAction* action =
      [factory actionToSaveToPhotosWithImageURL:fakeImageURL
                                       referrer:fakeImageReferrer
                                       webState:fakeWebState.get()
                                          block:nil];

  EXPECT_TRUE([expectedTitle isEqualToString:action.title]);
  EXPECT_EQ(expectedImage, action.image);
}