// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <ChromeWebView/ChromeWebView.h>
#import <Foundation/Foundation.h>

#import "ios/web_view/test/web_view_inttest_base.h"
#import "ios/web_view/test/web_view_test_util.h"
#import "net/base/apple/url_conversions.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "url/gurl.h"

namespace ios_web_view {

// Tests CWVNavigationDelegate.
class NavigationDelegateTest : public ios_web_view::WebViewInttestBase {
 public:
  NavigationDelegateTest()
      : mock_delegate_(
            OCMStrictProtocolMock(@protocol(CWVNavigationDelegate))) {
    [(id)mock_delegate_ setExpectationOrderMatters:YES];
    web_view_.navigationDelegate = mock_delegate_;
  }

  void SetUp() override {
    ios_web_view::WebViewInttestBase::SetUp();
    ASSERT_TRUE(test_server_->Start());
  }

  NSURL* GetEchoURL() {
    return net::NSURLWithGURL(test_server_->GetURL("/echo"));
  }

  NSURL* GetCloseSocketURL() {
    return net::NSURLWithGURL(test_server_->GetURL("/close-socket"));
  }

  id NavigationActionArg(NSURL* url, CWVNavigationType navigation_type) {
    return [OCMArg checkWithBlock:^(CWVNavigationAction* navigation_action) {
      return [navigation_action.request.URL isEqual:url] &&
             navigation_action.navigationType == navigation_type;
    }];
  }

  id NavigationResponseArg(NSURL* url, bool for_main_frame) {
    return
        [OCMArg checkWithBlock:^(CWVNavigationResponse* navigation_response) {
          return [navigation_response.response.URL isEqual:url] &&
                 navigation_response.forMainFrame == for_main_frame;
        }];
  }

  id<CWVNavigationDelegate> mock_delegate_;
};

// Tests that expected delegate methods are called for a successful request.
TEST_F(NavigationDelegateTest, RequestSucceeds) {
  // A request made with -loadRequest: has type CWVNavigationTypeTyped.
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationAction:NavigationActionArg(
                                          GetEchoURL(), CWVNavigationTypeTyped)
                      decisionHandler:[OCMArg checkWithBlock:^BOOL(void (
                                          ^decisionHandler)(
                                          CWVNavigationActionPolicy)) {
                        decisionHandler(CWVNavigationActionPolicyAllow);
                        return YES;
                      }]]);
  OCMExpect([mock_delegate_ webViewDidStartProvisionalNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidStartNavigation:web_view_]);
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationResponse:NavigationResponseArg(GetEchoURL(), YES)
                        decisionHandler:[OCMArg checkWithBlock:^BOOL(void (
                                            ^decisionHandler)(
                                            CWVNavigationResponsePolicy)) {
                          decisionHandler(CWVNavigationResponsePolicyAllow);
                          return YES;
                        }]]);
  OCMExpect([mock_delegate_ webViewDidCommitNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidFinishNavigation:web_view_]);

  ASSERT_TRUE(test::LoadUrl(web_view_, GetEchoURL()));
  EXPECT_OCMOCK_VERIFY(mock_delegate_);
}

// Tests that expected delegate methods are called for a failed request.
TEST_F(NavigationDelegateTest, RequestFails) {
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationAction:NavigationActionArg(
                                          GetCloseSocketURL(),
                                          CWVNavigationTypeTyped)
                      decisionHandler:[OCMArg checkWithBlock:^(void (
                                          ^decisionHandler)(
                                          CWVNavigationActionPolicy)) {
                        if (decisionHandler) {
                          decisionHandler(CWVNavigationActionPolicyAllow);
                        }
                        return YES;
                      }]]);
  OCMExpect([mock_delegate_ webViewDidStartProvisionalNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidStartNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidCommitNavigation:web_view_]);
  OCMExpect([mock_delegate_ webView:web_view_
         didFailNavigationWithError:[OCMArg any]]);

  ASSERT_TRUE(test::LoadUrl(web_view_, GetCloseSocketURL()));
  EXPECT_OCMOCK_VERIFY(mock_delegate_);

  // Wait for the error text to be injected to make sure that the JavaScript has
  // been correctly injected.
  ASSERT_TRUE(ios_web_view::test::WaitForWebViewContainingTextOrTimeout(
      web_view_, @"The network connection was lost."));
}

// Tests that a request is canceled and no further delegate methods are called
// when -shouldStartLoadWithRequest:navigationType: returns NO.
TEST_F(NavigationDelegateTest, CancelRequest) {
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationAction:NavigationActionArg(
                                          GetEchoURL(), CWVNavigationTypeTyped)
                      decisionHandler:[OCMArg checkWithBlock:^(void (
                                          ^decisionHandler)(
                                          CWVNavigationActionPolicy)) {
                        if (decisionHandler) {
                          decisionHandler(CWVNavigationActionPolicyCancel);
                        }
                        return YES;
                      }]]);

  ASSERT_TRUE(test::LoadUrl(web_view_, GetEchoURL()));
  EXPECT_OCMOCK_VERIFY(mock_delegate_);
}

// Tests that a response is canceled and no further delegate methods are called
// when -shouldContinueLoadWithResponse:forMainFrame: returns NO.
TEST_F(NavigationDelegateTest, CancelResponse) {
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationAction:NavigationActionArg(
                                          GetEchoURL(), CWVNavigationTypeTyped)
                      decisionHandler:[OCMArg checkWithBlock:^BOOL(void (
                                          ^decisionHandler)(
                                          CWVNavigationActionPolicy)) {
                        decisionHandler(CWVNavigationActionPolicyAllow);
                        return YES;
                      }]]);
  OCMExpect([mock_delegate_ webViewDidStartProvisionalNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidStartNavigation:web_view_]);
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationResponse:NavigationResponseArg(GetEchoURL(), YES)
                        decisionHandler:[OCMArg checkWithBlock:^(void (
                                            ^decisionHandler)(
                                            CWVNavigationResponsePolicy)) {
                          if (decisionHandler) {
                            decisionHandler(CWVNavigationResponsePolicyCancel);
                          }
                          return YES;
                        }]]);

  ASSERT_TRUE(test::LoadUrl(web_view_, GetEchoURL()));
  EXPECT_OCMOCK_VERIFY(mock_delegate_);
}

// Tests that same document navigations do not trigger delegate methods.
TEST_F(NavigationDelegateTest, SameDocumentNavigations) {
  // A request made with -loadRequest: has type CWVNavigationTypeTyped.
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationAction:NavigationActionArg(
                                          GetEchoURL(), CWVNavigationTypeTyped)
                      decisionHandler:[OCMArg checkWithBlock:^BOOL(void (
                                          ^decisionHandler)(
                                          CWVNavigationActionPolicy)) {
                        decisionHandler(CWVNavigationActionPolicyAllow);
                        return YES;
                      }]]);
  OCMExpect([mock_delegate_ webViewDidStartProvisionalNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidStartNavigation:web_view_]);
  OCMExpect([mock_delegate_ webView:web_view_
      decidePolicyForNavigationResponse:NavigationResponseArg(GetEchoURL(), YES)
                        decisionHandler:[OCMArg checkWithBlock:^BOOL(void (
                                            ^decisionHandler)(
                                            CWVNavigationResponsePolicy)) {
                          decisionHandler(CWVNavigationResponsePolicyAllow);
                          return YES;
                        }]]);
  OCMExpect([mock_delegate_ webViewDidCommitNavigation:web_view_]);
  OCMExpect([mock_delegate_ webViewDidFinishNavigation:web_view_]);

  ASSERT_TRUE(test::LoadUrl(web_view_, GetEchoURL()));

  EXPECT_OCMOCK_VERIFY(mock_delegate_);

  // Same document navigations should not trigger the delegate methods.
  NSError* error = nil;
  ASSERT_NSEQ(nil, test::EvaluateJavaScript(
                       web_view_, @"history.pushState({}, \"\");", &error));
  EXPECT_FALSE(error);

  EXPECT_OCMOCK_VERIFY(mock_delegate_);
}

}  // namespace ios_web_view
