// 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 <WebKit/WebKit.h>

#import "base/test/ios/wait_util.h"
#import "base/time/time.h"
#import "ios/testing/scoped_block_swizzler.h"
#import "ios/web/js_features/context_menu/context_menu_constants.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/test/web_test_with_web_controller.h"
#import "ios/web/web_state/ui/crw_context_menu_element_fetcher.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"

namespace {
// This is the timeout used while waiting for the JavaScript to complete. The
// general kWaitForJSCompletionTimeout isn't used because one of the test is
// supposed to not complete and so will wait for the whole duration of the
// timeout. This constant is smaller to speed tests up. This constant is used in
// both the "successful" JavaScript calls and the "failing" JavaScript calls. It
// ensures that in the context of this test, the JavaScript completes in the
// given timespan (and so it ensures that if the "failing" JavaScript tests
// pass, it is because the JavaScript isn't called and not because it didn't
// have time to complete).
constexpr base::TimeDelta kFetcherJSTimeout = base::Seconds(1);
}  // namespace

namespace web {
class CRWContextMenuElementFetcherTest : public WebTestWithWebController {
 public:
  void SetUp() override {
    WebTestWithWebState::SetUp();
    WKWebView* web_view = [web_controller() ensureWebViewCreated];
    fetcher_ =
        [[CRWContextMenuElementFetcher alloc] initWithWebView:web_view
                                                     webState:web_state()];
  }

  // Loads a page containing a link and waits until the link is present on the
  // page, making sure that the HTML is correctly injected.
  [[nodiscard]] bool LoadHtmlPage() {
    NSString* html =
        @"<html><head>"
         "<style>body { font-size:14em; }</style>"
         "<meta name=\"viewport\" content=\"user-scalable=no, width=100\">"
         "</head><body><p><a id=\"linkID\" "
         "href=\"http://destination/\">link</a></p></body></html>";

    LoadHtml(html);

    bool element_present = test::WaitForWebViewContainingElement(
        web_state(), [ElementSelector selectorWithElementID:"linkID"]);
    if (element_present) {
      // If the element is present, we still need a small delay to let all the
      // scripts be injected in the page.
      base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(0.5));
    }
    return element_present;
  }

  CRWContextMenuElementFetcher* GetFetcher() { return fetcher_; }

 private:
  std::unique_ptr<ScopedBlockSwizzler> swizzler_;
  CRWContextMenuElementFetcher* fetcher_;
};

// Tests that the fetcher is triggering a callback for one element.
TEST_F(CRWContextMenuElementFetcherTest, FetchOneElement) {
  EXPECT_TRUE(LoadHtmlPage());

  CRWContextMenuElementFetcher* fetcher = GetFetcher();
  __block bool callback_called = false;
  [fetcher fetchDOMElementAtPoint:CGPointMake(10, 10)
                completionHandler:^(const web::ContextMenuParams&) {
                  callback_called = true;
                }];

  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(kFetcherJSTimeout, ^{
    return callback_called;
  }));
}

// Tests that cancelled fetches don't trigger callback.
TEST_F(CRWContextMenuElementFetcherTest, CancelFetch) {
  EXPECT_TRUE(LoadHtmlPage());

  CRWContextMenuElementFetcher* fetcher = GetFetcher();
  __block bool callback_called = false;
  [fetcher fetchDOMElementAtPoint:CGPointMake(10, 10)
                completionHandler:^(const web::ContextMenuParams&) {
                  callback_called = true;
                }];
  [fetcher cancelFetches];

  // The callback should never be called.
  EXPECT_FALSE(
      base::test::ios::WaitUntilConditionOrTimeout(kFetcherJSTimeout, ^{
        return callback_called;
      }));
}

}  // namespace web
