chromium/ios/chrome/browser/ui/sharing/sharing_coordinator_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/sharing_coordinator.h"

#import <MaterialComponents/MaterialSnackbar.h>
#import <UIKit/UIKit.h>

#import "base/ios/block_types.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/values.h"
#import "components/bookmarks/browser/bookmark_node.h"
#import "components/bookmarks/test/bookmark_test_helpers.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_ios_unit_test_support.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/generate_qr_code_command.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/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_navigation_controller.h"
#import "ios/chrome/browser/ui/sharing/activity_services/activity_service_presentation.h"
#import "ios/chrome/browser/ui/sharing/activity_services/canonical_url_retriever.h"
#import "ios/chrome/browser/ui/sharing/sharing_params.h"
#import "ios/chrome/browser/ui/sharing/sharing_positioner.h"
#import "ios/chrome/test/scoped_key_window.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_frame.h"
#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/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 "url/gurl.h"

using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using bookmarks::BookmarkNode;

// Test fixture for testing SharingCoordinator.
class SharingCoordinatorTest : public BookmarkIOSUnitTestSupport {
 protected:
  SharingCoordinatorTest()
      : base_view_controller_([[UIViewController alloc] init]),
        fake_origin_view_([[UIView alloc] init]),
        test_scenario_(SharingScenario::TabShareButton) {
    [scoped_key_window_.Get() setRootViewController:base_view_controller_];
  }

  void SetUp() override {
    BookmarkIOSUnitTestSupport::SetUp();
    snackbar_handler_ = OCMStrictProtocolMock(@protocol(SnackbarCommands));
    [browser_->GetCommandDispatcher()
        startDispatchingToTarget:snackbar_handler_
                     forProtocol:@protocol(SnackbarCommands)];

    [browser_->GetCommandDispatcher()
        startDispatchingToTarget:OCMStrictProtocolMock(
                                     @protocol(BookmarksCommands))
                     forProtocol:@protocol(BookmarksCommands)];
    [browser_->GetCommandDispatcher()
        startDispatchingToTarget:OCMStrictProtocolMock(@protocol(HelpCommands))
                     forProtocol:@protocol(HelpCommands)];
  }

  void AppendNewWebState(std::unique_ptr<web::FakeWebState> web_state) {
    browser_->GetWebStateList()->InsertWebState(
        std::move(web_state),
        WebStateList::InsertionParams::Automatic().Activate());
  }

  ScopedKeyWindow scoped_key_window_;
  UIViewController* base_view_controller_;
  UIView* fake_origin_view_;
  id snackbar_handler_;
  SharingScenario test_scenario_;
};

// Tests that the start method shares the current page and ends up presenting
// a UIActivityViewController.
TEST_F(SharingCoordinatorTest, Start_ShareCurrentPage) {
  // Create a test web state.
  GURL test_url = GURL("https://example.com");
  base::Value url_value = base::Value(test_url.spec());
  auto test_web_state = std::make_unique<web::FakeWebState>();
  test_web_state->SetNavigationManager(
      std::make_unique<web::FakeNavigationManager>());
  test_web_state->SetCurrentURL(test_url);
  test_web_state->SetBrowserState(browser_->GetBrowserState());

  auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
  web::FakeWebFramesManager* frames_manager_ptr = frames_manager.get();
  test_web_state->SetWebFramesManager(std::move(frames_manager));

  auto main_frame = web::FakeWebFrame::CreateMainWebFrame(test_url);
  web::FakeWebFrame* main_frame_ptr = main_frame.get();
  frames_manager_ptr->AddWebFrame(std::move(main_frame));

  main_frame_ptr->AddResultForExecutedJs(
      &url_value, activity_services::kCanonicalURLScript);

  AppendNewWebState(std::move(test_web_state));

  SharingParams* params =
      [[SharingParams alloc] initWithScenario:test_scenario_];

  SharingCoordinator* coordinator = [[SharingCoordinator alloc]
      initWithBaseViewController:base_view_controller_
                         browser:browser_.get()
                          params:params
                      originView:fake_origin_view_];

  __block bool completion_handler_called = false;
  id vc_partial_mock = OCMPartialMock(base_view_controller_);
  [[vc_partial_mock expect]
      presentViewController:[OCMArg checkWithBlock:^BOOL(
                                        UIViewController* viewController) {
        if ([viewController isKindOfClass:[UIActivityViewController class]]) {
          completion_handler_called = true;
          return YES;
        }
        return NO;
      }]
                   animated:YES
                 completion:nil];

  [coordinator start];

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return completion_handler_called;
  }));

  // Verify that the positioning is correct.
  auto activityHandler =
      static_cast<id<SharingPositioner, ActivityServicePresentation>>(
          coordinator);
  EXPECT_EQ(fake_origin_view_, activityHandler.sourceView);
  EXPECT_TRUE(
      CGRectEqualToRect(fake_origin_view_.bounds, activityHandler.sourceRect));

  [activityHandler activityServiceDidEndPresenting];

  [vc_partial_mock verify];
  [coordinator stop];
}

// Tests that the coordinator handles the QRGenerationCommands protocol.
TEST_F(SharingCoordinatorTest, GenerateQRCode) {
  SharingParams* params =
      [[SharingParams alloc] initWithScenario:test_scenario_];
  SharingCoordinator* coordinator = [[SharingCoordinator alloc]
      initWithBaseViewController:base_view_controller_
                         browser:browser_.get()
                          params:params
                      originView:fake_origin_view_];

  id vc_partial_mock = OCMPartialMock(base_view_controller_);
  [[vc_partial_mock expect] presentViewController:[OCMArg any]
                                         animated:YES
                                       completion:nil];

  auto handler = static_cast<id<QRGenerationCommands>>(coordinator);
  [handler generateQRCode:[[GenerateQRCodeCommand alloc]
                              initWithURL:GURL("https://example.com")
                                    title:@"Some Title"]];

  [vc_partial_mock verify];

  [[vc_partial_mock expect] dismissViewControllerAnimated:YES completion:nil];

  [handler hideQRCode];

  [vc_partial_mock verify];
  [coordinator stop];
}

// Tests that the start method shares the given URL and ends up presenting
// a UIActivityViewController.
TEST_F(SharingCoordinatorTest, Start_ShareURL) {
  GURL testURL = GURL("https://example.com");
  NSString* testTitle = @"Some title";
  SharingParams* params = [[SharingParams alloc] initWithURL:testURL
                                                       title:testTitle
                                                    scenario:test_scenario_];
  SharingCoordinator* coordinator = [[SharingCoordinator alloc]
      initWithBaseViewController:base_view_controller_
                         browser:browser_.get()
                          params:params
                      originView:fake_origin_view_];

  id vc_partial_mock = OCMPartialMock(base_view_controller_);
  [[vc_partial_mock expect]
      presentViewController:[OCMArg checkWithBlock:^BOOL(
                                        UIViewController* viewController) {
        if ([viewController isKindOfClass:[UIActivityViewController class]]) {
          return YES;
        }
        return NO;
      }]
                   animated:YES
                 completion:nil];

  [coordinator start];

  [vc_partial_mock verify];

  // Make sure share sheet finishes it's init (which means calling
  // canPerformWithActivityItems and reading prefs) before the
  // WebTaskEnvironment is shut down.
  base::RunLoop().RunUntilIdle();
  [coordinator stop];
}