chromium/ios/chrome/browser/ui/sharing/activity_services/activity_service_mediator_unittest.mm

// Copyright 2020 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/sharing/activity_services/activity_service_mediator.h"

#import "base/test/metrics/histogram_tester.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/testing_pref_service.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/find_in_page_commands.h"
#import "ios/chrome/browser/shared/public/commands/help_commands.h"
#import "ios/chrome/browser/shared/public/commands/qr_generation_commands.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/bookmark_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/copy_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/find_in_page_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/generate_qr_code_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/print_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/reading_list_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/request_desktop_or_mobile_site_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activities/send_tab_to_self_activity.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activity_type_util.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_image_source.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_item_source.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_item_thumbnail_generator.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_text_source.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/chrome_activity_url_source.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/share_image_data.h"
#import "ios/chrome/browser/ui/sharing/activity_services/data/share_to_data.h"
#import "ios/chrome/browser/ui/sharing/sharing_scenario.h"
#import "ios/web/common/user_agent.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"

@protocol HandlerProtocols <FindInPageCommands>
@end

class ActivityServiceMediatorTest : public PlatformTest {
 protected:
  void SetUp() override {
    PlatformTest::SetUp();

    pref_service_ = std::make_unique<TestingPrefServiceSimple>();

    mocked_handler_ = OCMStrictProtocolMock(@protocol(HandlerProtocols));
    mocked_bookmarks_handler_ =
        OCMStrictProtocolMock(@protocol(BookmarksCommands));
    mocked_help_handler_ = OCMStrictProtocolMock(@protocol(HelpCommands));
    mocked_qr_generation_handler_ =
        OCMStrictProtocolMock(@protocol(QRGenerationCommands));
    mocked_thumbnail_generator_ =
        OCMStrictClassMock([ChromeActivityItemThumbnailGenerator class]);

    mediator_ = [[ActivityServiceMediator alloc]
                initWithHandler:mocked_handler_
               bookmarksHandler:mocked_bookmarks_handler_
                    helpHandler:mocked_help_handler_
            qrGenerationHandler:mocked_qr_generation_handler_
                    prefService:pref_service_.get()
                  bookmarkModel:nil
             baseViewController:nil
                navigationAgent:nil
        readingListBrowserAgent:nil];

    pref_service_->registry()->RegisterBooleanPref(prefs::kPrintingEnabled,
                                                   true);
    pref_service_->registry()->RegisterIntegerPref(prefs::kIosShareChromeCount,
                                                   0);
    pref_service_->registry()->RegisterTimePref(prefs::kIosShareChromeLastShare,
                                                base::Time());
  }

  void VerifyTypes(NSArray* activities, NSArray* expected_types) {
    EXPECT_EQ([expected_types count], [activities count]);
    for (unsigned int i = 0U; i < [activities count]; i++) {
      EXPECT_TRUE([activities[i] isKindOfClass:expected_types[i]]);
    }
  }

  id mocked_handler_;
  id mocked_bookmarks_handler_;
  id mocked_help_handler_;
  id mocked_qr_generation_handler_;
  id mocked_thumbnail_generator_;
  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
  base::HistogramTester histograms_tester_;

  ActivityServiceMediator* mediator_;
};

// Tests that only one ChromeActivityURLSource is initialized from a ShareToData
// instance without additional text.
TEST_F(ActivityServiceMediatorTest, ActivityItemsForMulitpleDataItems_Success) {
  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("https://www.google.com/")
                                 visibleURL:GURL("https://google.com/")
                                      title:@"Some Title"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray<id<ChromeActivityItemSource>>* activityItems =
      [mediator_ activityItemsForDataItems:@[ data ]];

  EXPECT_EQ(1U, [activityItems count]);
  EXPECT_TRUE([activityItems[0] isKindOfClass:[ChromeActivityURLSource class]]);
}

// Tests that two activity items are created from a ShareToData instance with
// additional text.
TEST_F(ActivityServiceMediatorTest,
       ActivityItemsForData_WithAdditionalText_Success) {
  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("https://www.google.com/")
                                 visibleURL:GURL("https://google.com/")
                                      title:@"Some Title"
                             additionalText:@"Foo, bar!"
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray<id<ChromeActivityItemSource>>* activityItems =
      [mediator_ activityItemsForDataItems:@[ data ]];

  EXPECT_EQ(2U, [activityItems count]);
  EXPECT_TRUE(
      [activityItems[0] isKindOfClass:[ChromeActivityTextSource class]]);
  EXPECT_TRUE([activityItems[1] isKindOfClass:[ChromeActivityURLSource class]]);
}

// Tests that separate ChromeActivityURLSource instances are initialized for
// each ShareToData instance.
TEST_F(ActivityServiceMediatorTest,
       ActivityItemsForData_NoAdditionalText_Success) {
  ShareToData* data1 =
      [[ShareToData alloc] initWithShareURL:GURL("https://www.google.com/")
                                 visibleURL:GURL("https://google.com/")
                                      title:@"Some Title"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  ShareToData* data2 =
      [[ShareToData alloc] initWithShareURL:GURL("https://www.example.com/")
                                 visibleURL:GURL("https://example.com/")
                                      title:@"Another Title"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray<id<ChromeActivityItemSource>>* activityItems =
      [mediator_ activityItemsForDataItems:@[ data1, data2 ]];

  EXPECT_EQ(2U, [activityItems count]);
  EXPECT_TRUE([activityItems[0] isKindOfClass:[ChromeActivityURLSource class]]);
  EXPECT_TRUE([activityItems[1] isKindOfClass:[ChromeActivityURLSource class]]);
}

// Tests that only the CopyActivity and PrintActivity get added for a page that
// is not HTTP or HTTPS.
TEST_F(ActivityServiceMediatorTest, ActivitiesForData_NotHTTPOrHTTPS) {
  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("chrome://chromium.org/")
                                 visibleURL:GURL("chrome://chromium.org/")
                                      title:@"baz"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray* activities = [mediator_ applicationActivitiesForDataItems:@[ data ]];

  // Verify activities' types.
  VerifyTypes(activities, @[ [CopyActivity class], [PrintActivity class] ]);
}

// Tests that the right activities are added in order for an HTTP page.
TEST_F(ActivityServiceMediatorTest, ActivitiesForData_HTTP) {
  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("http://example.com")
                                 visibleURL:GURL("http://example.com")
                                      title:@"baz"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray* activities = [mediator_ applicationActivitiesForDataItems:@[ data ]];

  // Verify activities' types.
  VerifyTypes(activities, @[
    [CopyActivity class], [SendTabToSelfActivity class],
    [ReadingListActivity class], [BookmarkActivity class],
    [GenerateQrCodeActivity class], [FindInPageActivity class],
    [RequestDesktopOrMobileSiteActivity class], [PrintActivity class]
  ]);
}

// Tests that the right activities are added in order for an HTTPS page.
TEST_F(ActivityServiceMediatorTest, ActivitiesForData_HTTPS) {
  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("https://example.com")
                                 visibleURL:GURL("https://example.com")
                                      title:@"baz"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray* activities = [mediator_ applicationActivitiesForDataItems:@[ data ]];

  // Verify activities' types.
  VerifyTypes(activities, @[
    [CopyActivity class], [SendTabToSelfActivity class],
    [ReadingListActivity class], [BookmarkActivity class],
    [GenerateQrCodeActivity class], [FindInPageActivity class],
    [RequestDesktopOrMobileSiteActivity class], [PrintActivity class]
  ]);
}

// Tests that only the CopyActivity is available for multiple data items.
TEST_F(ActivityServiceMediatorTest, ActivitiesForMultipleDataItems) {
  ShareToData* data1 =
      [[ShareToData alloc] initWithShareURL:GURL("https://google.com")
                                 visibleURL:GURL("https://google.com")
                                      title:@"Title"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];
  ShareToData* data2 =
      [[ShareToData alloc] initWithShareURL:GURL("https://example.com")
                                 visibleURL:GURL("https://example.com")
                                      title:@"baz"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray* activities =
      [mediator_ applicationActivitiesForDataItems:@[ data1, data2 ]];

  VerifyTypes(activities, @[ [CopyActivity class] ]);
}

// Tests that only one ChromeActivityImageSource is initialized from a
// ShareIamgeData instance.
TEST_F(ActivityServiceMediatorTest, ActivityItemsForImageData_Success) {
  ShareImageData* data =
      [[ShareImageData alloc] initWithImage:[[UIImage alloc] init]
                                      title:@"some title"];

  NSArray<ChromeActivityImageSource*>* activityItems =
      [mediator_ activityItemsForImageData:data];

  EXPECT_EQ(1U, [activityItems count]);
}

// Tests that the right activities are added in order for an image.
TEST_F(ActivityServiceMediatorTest, ActivitiesForImageData) {
  ShareImageData* data =
      [[ShareImageData alloc] initWithImage:[[UIImage alloc] init]
                                      title:@"some title"];

  NSArray* activities = [mediator_ applicationActivitiesForImageData:data];

  // For now, we only customize the print activity.
  EXPECT_EQ(1U, [activities count]);
  VerifyTypes(activities, @[ [PrintActivity class] ]);
}

// Tests that computing the list of excluded activities works for one URL.
TEST_F(ActivityServiceMediatorTest, ExcludedActivityTypes_SingleItemURL) {
  ChromeActivityURLSource* activityURLSource = [[ChromeActivityURLSource alloc]
      initWithShareURL:[NSURL URLWithString:@"https://example.com"]
               subject:@"Does not matter"];

  NSSet* expectedSet = [NSSet setWithArray:@[
    UIActivityTypeAddToReadingList, UIActivityTypeCopyToPasteboard,
    UIActivityTypePrint, UIActivityTypeSaveToCameraRoll
  ]];
  NSSet* mediatorSet =
      [mediator_ excludedActivityTypesForItems:@[ activityURLSource ]];

  EXPECT_NSEQ(expectedSet, activityURLSource.excludedActivityTypes);
  EXPECT_NSEQ(expectedSet, mediatorSet);
}

// Tests that computing the list of excluded activities works for one image.
TEST_F(ActivityServiceMediatorTest, ExcludedActivityTypes_SingleItemImage) {
  ChromeActivityImageSource* activityImageSource =
      [[ChromeActivityImageSource alloc] initWithImage:[[UIImage alloc] init]
                                                 title:@"something"];
  NSSet* expectedSet = [NSSet setWithArray:@[
    UIActivityTypeAssignToContact,
    UIActivityTypePrint,
  ]];

  NSSet* mediatorSet =
      [mediator_ excludedActivityTypesForItems:@[ activityImageSource ]];

  EXPECT_NSEQ(expectedSet, activityImageSource.excludedActivityTypes);
  EXPECT_NSEQ(expectedSet, mediatorSet);
}

// Tests that computing the list of excluded activities works for two item.
TEST_F(ActivityServiceMediatorTest, ExcludedActivityTypes_TwoItems) {
  ChromeActivityURLSource* activityURLSource = [[ChromeActivityURLSource alloc]
      initWithShareURL:[NSURL URLWithString:@"https://example.com"]
               subject:@"Does not matter"];
  ChromeActivityImageSource* activityImageSource =
      [[ChromeActivityImageSource alloc] initWithImage:[[UIImage alloc] init]
                                                 title:@"something"];

  NSMutableSet* expectedSet = [[NSMutableSet alloc]
      initWithSet:activityURLSource.excludedActivityTypes];
  [expectedSet unionSet:activityImageSource.excludedActivityTypes];

  NSSet* computedExclusion = [mediator_ excludedActivityTypesForItems:@[
    activityURLSource, activityImageSource
  ]];

  EXPECT_TRUE([expectedSet isEqualToSet:computedExclusion]);
}

// Tests that successful action completion is wired to log a histogram.
TEST_F(ActivityServiceMediatorTest, ShareFinished_Success) {
  // Since mocked_handler_ is a strict mock, any call to its methods would make
  // the test fail.
  NSString* copyActivityString = @"com.google.chrome.copyActivity";
  [mediator_ shareFinishedWithScenario:SharingScenario::TabShareButton
                          activityType:copyActivityString
                             completed:YES];

  // Verify histogram is logged. Values are hardcoded as they are encapsulated
  // away.
  const char histogramName[] = "Mobile.Share.TabShareButton.Actions";
  int copyAction = 3;
  histograms_tester_.ExpectBucketCount(histogramName, copyAction, 1);
}

// Tests that successful action completion in ShareChrome scenario stores prefs
TEST_F(ActivityServiceMediatorTest, ShareFinished_SuccessShareChrome) {
  int initialCount = pref_service_->GetInteger(prefs::kIosShareChromeCount);
  base::Time startTime = base::Time::Now();
  // Since mocked_handler_ is a strict mock, any call to its methods would make
  // the test fail.
  NSString* copyActivityString = @"com.google.chrome.copyActivity";
  [mediator_ shareFinishedWithScenario:SharingScenario::ShareChrome
                          activityType:copyActivityString
                             completed:YES];
  int count = pref_service_->GetInteger(prefs::kIosShareChromeCount);
  base::Time lastShare =
      pref_service_->GetTime(prefs::kIosShareChromeLastShare);
  EXPECT_EQ(count, initialCount + 1);
  EXPECT_GT(lastShare, startTime);
}

TEST_F(ActivityServiceMediatorTest, ShareFinished_Cancel) {
  // Since mocked_handler_ is a strict mock, any call to its methods would make
  // the test fail. That is our success condition.
  NSString* copyActivityString = @"com.google.chrome.copyActivity";
  [mediator_ shareFinishedWithScenario:SharingScenario::TabShareButton
                          activityType:copyActivityString
                             completed:NO];

  // Verify histogram is logged. Values are hardcoded as they are encapsulated
  // away.
  const char histogramName[] = "Mobile.Share.TabShareButton.Actions";
  int cancelAction = 1;
  histograms_tester_.ExpectBucketCount(histogramName, cancelAction, 1);
}

TEST_F(ActivityServiceMediatorTest, ShareCancelled) {
  // Since mocked_handler_ is a strict mock, any call to its methods would make
  // the test fail. That is our success condition.
  [mediator_ shareFinishedWithScenario:SharingScenario::TabShareButton
                          activityType:nil
                             completed:NO];

  // Verify histogram is logged. Values are hardcoded as they are encapsulated
  // away.
  const char histogramName[] = "Mobile.Share.TabShareButton.Actions";
  int cancelAction = 1;
  histograms_tester_.ExpectBucketCount(histogramName, cancelAction, 1);
}

TEST_F(ActivityServiceMediatorTest, PrintPrefDisabled) {
  pref_service_->SetUserPref(prefs::kPrintingEnabled,
                             std::make_unique<base::Value>(false));

  ShareToData* data =
      [[ShareToData alloc] initWithShareURL:GURL("http://example.com")
                                 visibleURL:GURL("http://example.com")
                                      title:@"baz"
                             additionalText:nil
                            isOriginalTitle:YES
                            isPagePrintable:YES
                           isPageSearchable:YES
                           canSendTabToSelf:YES
                                  userAgent:web::UserAgentType::MOBILE
                         thumbnailGenerator:mocked_thumbnail_generator_
                               linkMetadata:nil];

  NSArray* activities = [mediator_ applicationActivitiesForDataItems:@[ data ]];

  // Verify activities' types.
  VerifyTypes(activities, @[
    [CopyActivity class], [SendTabToSelfActivity class],
    [ReadingListActivity class], [BookmarkActivity class],
    [GenerateQrCodeActivity class], [FindInPageActivity class],
    [RequestDesktopOrMobileSiteActivity class]
  ]);

  // Verify activities' size.
  EXPECT_EQ(7U, [activities count]);
}