// Copyright 2016 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/web/public/web_state_delegate_bridge.h"

#import <Foundation/Foundation.h>

#import <memory>

#import "base/functional/bind.h"
#import "base/functional/callback_helpers.h"
#import "base/strings/utf_string_conversions.h"
#import "ios/web/public/test/crw_fake_web_state_delegate.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/ui/context_menu_params.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/page_transition_types.h"

// Class which conforms to CRWWebStateDelegate protocol, but does not implement
// any optional methods.
@interface TestEmptyWebStateDelegate : NSObject <CRWWebStateDelegate>
@end

@implementation TestEmptyWebStateDelegate
@end

namespace web {

// Test fixture to test WebStateDelegateBridge class.
class WebStateDelegateBridgeTest : public PlatformTest {
 protected:
  void SetUp() override {
    PlatformTest::SetUp();

    delegate_ = [[CRWFakeWebStateDelegate alloc] init];
    empty_delegate_ = [[TestEmptyWebStateDelegate alloc] init];

    bridge_.reset(new WebStateDelegateBridge(delegate_));
    empty_delegate_bridge_.reset(new WebStateDelegateBridge(empty_delegate_));
  }

  void TearDown() override { PlatformTest::TearDown(); }

  CRWFakeWebStateDelegate* delegate_;
  id empty_delegate_;
  std::unique_ptr<WebStateDelegateBridge> bridge_;
  std::unique_ptr<WebStateDelegateBridge> empty_delegate_bridge_;
  web::FakeWebState fake_web_state_;
};

// Tests `webState:createNewWebStateForURL:openerURL:initiatedByUser:`
// forwarding.
TEST_F(WebStateDelegateBridgeTest, CreateNewWebState) {
  ASSERT_FALSE([delegate_ webState]);
  ASSERT_FALSE([delegate_ webStateCreationRequested]);

  EXPECT_FALSE(
      bridge_->CreateNewWebState(&fake_web_state_, GURL(), GURL(), true));

  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  ASSERT_TRUE([delegate_ webStateCreationRequested]);
}

// Tests `closeWebState:` forwarding.
TEST_F(WebStateDelegateBridgeTest, CloseWebState) {
  ASSERT_FALSE([delegate_ webState]);
  ASSERT_FALSE([delegate_ webStateClosingRequested]);

  bridge_->CloseWebState(&fake_web_state_);

  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  ASSERT_TRUE([delegate_ webStateClosingRequested]);
}

// Tests `webState:openURLWithParams:` forwarding.
TEST_F(WebStateDelegateBridgeTest, OpenURLFromWebState) {
  ASSERT_FALSE([delegate_ webState]);
  ASSERT_FALSE([delegate_ openURLParams]);

  web::WebState::OpenURLParams params(
      GURL("https://chromium.test/"), GURL("https://virtual.chromium.test/"),
      web::Referrer(GURL("https://chromium2.test/"), ReferrerPolicyNever),
      WindowOpenDisposition::NEW_WINDOW, ui::PAGE_TRANSITION_FORM_SUBMIT, true);
  EXPECT_EQ(&fake_web_state_,
            bridge_->OpenURLFromWebState(&fake_web_state_, params));

  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  const web::WebState::OpenURLParams* result_params = [delegate_ openURLParams];
  ASSERT_TRUE(result_params);
  EXPECT_EQ(params.url, result_params->url);
  EXPECT_EQ(params.virtual_url, result_params->virtual_url);
  EXPECT_EQ(params.referrer.url, result_params->referrer.url);
  EXPECT_EQ(params.referrer.policy, result_params->referrer.policy);
  EXPECT_EQ(params.disposition, result_params->disposition);
  EXPECT_EQ(static_cast<int>(params.transition),
            static_cast<int>(result_params->transition));
  EXPECT_EQ(params.is_renderer_initiated, result_params->is_renderer_initiated);
}

// Tests `ShowRepostFormWarningDialog` forwarding.
TEST_F(WebStateDelegateBridgeTest, ShowRepostFormWarningDialog) {
  EXPECT_FALSE([delegate_ repostFormWarningRequested]);
  EXPECT_FALSE([delegate_ webState]);
  base::OnceCallback<void(bool)> callback;
  bridge_->ShowRepostFormWarningDialog(
      &fake_web_state_, web::FormWarningType::kRepost, std::move(callback));
  EXPECT_TRUE([delegate_ repostFormWarningRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
}

// Tests `ShowRepostFormWarningDialog` forwarding to delegate which does not
// implement `webState:runRepostFormDialogWithCompletionHandler:` method.
TEST_F(WebStateDelegateBridgeTest, ShowRepostFormWarningWithNoDelegateMethod) {
  __block bool callback_called = false;
  empty_delegate_bridge_->ShowRepostFormWarningDialog(
      nullptr, web::FormWarningType::kRepost,
      base::BindOnce(^(bool should_repost) {
        EXPECT_TRUE(should_repost);
        callback_called = true;
      }));
  EXPECT_TRUE(callback_called);
}

// Tests `GetJavaScriptDialogPresenter` forwarding.
TEST_F(WebStateDelegateBridgeTest, GetJavaScriptDialogPresenter) {
  EXPECT_FALSE([delegate_ javaScriptDialogPresenterRequested]);
  bridge_->GetJavaScriptDialogPresenter(nullptr);
  EXPECT_TRUE([delegate_ javaScriptDialogPresenterRequested]);
}

// Tests `HandlePermissionsDecisionRequest` forwarding.
TEST_F(WebStateDelegateBridgeTest, HandlePermissionsDecisionRequest) {
  __block bool callback_called = false;
  EXPECT_FALSE([delegate_ permissionsRequestHandled]);
  EXPECT_FALSE([delegate_ webState]);
  bridge_->HandlePermissionsDecisionRequest(
      &fake_web_state_, @[], ^(PermissionDecision decision) {
        EXPECT_EQ(decision, PermissionDecisionGrant);
        callback_called = true;
      });
  EXPECT_TRUE([delegate_ permissionsRequestHandled]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  EXPECT_TRUE(callback_called);
}

// Tests `HandlePermissionsDecisionRequest` forwarding to delegate which does
// not implement `webState:handlePermissions:decisionHandler:` method.
TEST_F(WebStateDelegateBridgeTest,
       HandlePermissionsDecisionRequestWithNoDelegateMethod) {
  __block bool callback_called = false;
  empty_delegate_bridge_->HandlePermissionsDecisionRequest(
      nullptr, @[], ^(PermissionDecision decision) {
        // Default decision `PermissionDecisionShowDefaultPrompt` will be used
        // when delegate doesn't implement
        // `webState:handlePermissions:decisionHandler:` method to handle the
        // permissions.
        EXPECT_EQ(decision, PermissionDecisionShowDefaultPrompt);
        callback_called = true;
      });
  EXPECT_TRUE(callback_called);
}

// Tests `OnAuthRequired` forwarding.
TEST_F(WebStateDelegateBridgeTest, OnHTTPAuthRequired) {
  EXPECT_FALSE([delegate_ httpAuthenticationRequested]);
  EXPECT_FALSE([delegate_ webState]);
  NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init];
  NSURLCredential* credential = [[NSURLCredential alloc] init];
  WebStateDelegate::HTTPAuthCallback callback = base::DoNothing();
  bridge_->OnAuthRequired(&fake_web_state_, protection_space, credential,
                          std::move(callback));
  EXPECT_TRUE([delegate_ httpAuthenticationRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
}

// Tests `OnAuthRequired` for client certs forwarding.
TEST_F(WebStateDelegateBridgeTest, OnClientCertAuthRequired) {
  EXPECT_FALSE([delegate_ clientCertAuthenticationRequested]);
  EXPECT_FALSE([delegate_ webState]);
  NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init];
  WebStateDelegate::ClientCertAuthCallback callback = base::DoNothing();
  bridge_->OnAuthRequired(&fake_web_state_, protection_space,
                          std::move(callback));
  EXPECT_TRUE([delegate_ clientCertAuthenticationRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
}

// Tests `OnAuthRequired` for client certs forwarding to delegate which does not
// implement
// `webState:didRequestClientCertAuthForProtectionSpace:completionHandler:`
// method.
TEST_F(WebStateDelegateBridgeTest,
       OnClientCertAuthRequiredWithNoDelegateMethod) {
  __block bool callback_called = false;
  WebStateDelegate::ClientCertAuthCallback callback =
      base::BindOnce(^(SecIdentityRef identity) {
        EXPECT_FALSE(identity);
        callback_called = true;
      });
  empty_delegate_bridge_->OnAuthRequired(&fake_web_state_, nil,
                                         std::move(callback));
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowCopy` forwarding.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowCopy) {
  ASSERT_FALSE([delegate_ copyAllowedRequested]);
  ASSERT_FALSE([delegate_ webState]);
  __block bool callback_called = false;
  bridge_->ShouldAllowCopy(&fake_web_state_, base::BindOnce(^(bool allowed) {
    EXPECT_TRUE(allowed);
    callback_called = true;
  }));
  EXPECT_TRUE([delegate_ copyAllowedRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowCopy` forwarding to delegate which does not
// implement `webState:shouldAllowCopyWithDecisionHandler:` method.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowCopyWithNoDelegateMethod) {
  __block bool callback_called = false;
  empty_delegate_bridge_->ShouldAllowCopy(nullptr,
                                          base::BindOnce(^(bool allowed) {
                                            EXPECT_TRUE(allowed);
                                            callback_called = true;
                                          }));
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowPaste` forwarding.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowPaste) {
  ASSERT_FALSE([delegate_ pasteAllowedRequested]);
  ASSERT_FALSE([delegate_ webState]);
  __block bool callback_called = false;
  bridge_->ShouldAllowPaste(&fake_web_state_, base::BindOnce(^(bool allowed) {
    EXPECT_TRUE(allowed);
    callback_called = true;
  }));
  EXPECT_TRUE([delegate_ pasteAllowedRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowPaste` forwarding to delegate which does not
// implement `webState:shouldAllowPasteWithDecisionHandler:` method.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowPasteWithNoDelegateMethod) {
  __block bool callback_called = false;
  empty_delegate_bridge_->ShouldAllowPaste(nullptr,
                                           base::BindOnce(^(bool allowed) {
                                             EXPECT_TRUE(allowed);
                                             callback_called = true;
                                           }));
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowCut` forwarding.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowCut) {
  ASSERT_FALSE([delegate_ cutAllowedRequested]);
  ASSERT_FALSE([delegate_ webState]);
  __block bool callback_called = false;
  bridge_->ShouldAllowCut(&fake_web_state_, base::BindOnce(^(bool allowed) {
    EXPECT_TRUE(allowed);
    callback_called = true;
  }));
  EXPECT_TRUE([delegate_ cutAllowedRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
  EXPECT_TRUE(callback_called);
}

// Tests `ShouldAllowCut` forwarding to delegate which does not
// implement `webState:shouldAllowCutWithDecisionHandler:` method.
TEST_F(WebStateDelegateBridgeTest, ShouldAllowCutWithNoDelegateMethod) {
  __block bool callback_called = false;
  empty_delegate_bridge_->ShouldAllowCut(nullptr,
                                         base::BindOnce(^(bool allowed) {
                                           EXPECT_TRUE(allowed);
                                           callback_called = true;
                                         }));
  EXPECT_TRUE(callback_called);
}

// Tests `DidFinishClipboardRead` forwarding.
TEST_F(WebStateDelegateBridgeTest, DidFinishClipboardRead) {
  EXPECT_FALSE([delegate_ didFinishClipboardReadRequested]);
  EXPECT_FALSE([delegate_ webState]);
  bridge_->DidFinishClipboardRead(&fake_web_state_);
  EXPECT_TRUE([delegate_ didFinishClipboardReadRequested]);
  EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
}

}  // namespace web
