chromium/ios/chrome/browser/follow/model/follow_java_script_feature_unittest.mm

// Copyright 2022 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/follow/model/follow_java_script_feature.h"

#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/web/model/chrome_web_client.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_state.h"
#import "net/base/apple/url_conversions.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

class FollowJavaScriptFeatureTest : public PlatformTest {
 public:
  FollowJavaScriptFeatureTest()
      : web_client_(std::make_unique<ChromeWebClient>()) {
    browser_state_ = TestChromeBrowserState::Builder().Build();

    web::WebState::CreateParams params(browser_state_.get());
    web_state_ = web::WebState::Create(params);
    web_state_->GetView();
    web_state_->SetKeepRenderProcessAlive(true);
  }

  web::WebState* web_state() { return web_state_.get(); }

 protected:
  web::ScopedTestingWebClient web_client_;
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<web::WebState> web_state_;
};

// Tests that the completion block is called with no results for an empty page.
TEST_F(FollowJavaScriptFeatureTest, NoRSSLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(@"<html></html>", example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        EXPECT_EQ(0ul, urls.RSSURLs.count);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single alternate rss+xml
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleAlternateRssXMLLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(
      @"<html><head>"
       "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss\"/>"
       "</head></html>",
      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single alternate rss+atom
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleAlternateRssAtomLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(
      @"<html><head>"
       "<link rel=\"alternate\" type=\"application/rss+atom\" href=\"/rss\"/>"
       "</head></html>",
      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single alternate atom+xml
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleAlternateAtomXMLLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(
      @"<html><head>"
       "<link rel=\"alternate\" type=\"application/atom+xml\" href=\"/rss\"/>"
       "</head></html>",
      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single service.feed rss+xml
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleServiceFeedRssXMLLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(
      @"<html><head>"
       "<link rel=\"service.feed\" type=\"application/rss+xml\" href=\"/rss\"/>"
       "</head></html>",
      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single service.feed rss+atom
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleServiceFeedRssAtomLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(@"<html><head>"
                       "<link rel=\"service.feed\" "
                       "type=\"application/rss+atom\" href=\"/rss\"/>"
                       "</head></html>",
                      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with a single service.feed atom+xml
// link.
TEST_F(FollowJavaScriptFeatureTest, SingleServiceFeedAtomXMLLink) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(@"<html><head>"
                       "<link rel=\"service.feed\" "
                       "type=\"application/atom+xml\" href=\"/rss\"/>"
                       "</head></html>",
                      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(1ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}

// Tests that the completion block is called with multiple rss links.
TEST_F(FollowJavaScriptFeatureTest, MultipleRSSLinks) {
  GURL example_url = GURL("http://example.com");
  web::test::LoadHtml(
      @"<html><head>"
       "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss\"/>"
       "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss2\"/>"
       "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"/rss3\"/>"
       "</head></html>",
      example_url, web_state());

  __block bool completion_called = false;

  FollowJavaScriptFeature::GetInstance()->GetWebPageURLs(
      web_state(), base::BindOnce(^(WebPageURLs* urls) {
        ASSERT_TRUE(urls);
        EXPECT_NSEQ(net::NSURLWithGURL(example_url), urls.URL);
        ASSERT_EQ(3ul, urls.RSSURLs.count);
        EXPECT_NSEQ(@"http://example.com/rss",
                    [urls.RSSURLs[0] absoluteString]);
        EXPECT_NSEQ(@"http://example.com/rss2",
                    [urls.RSSURLs[1] absoluteString]);
        EXPECT_NSEQ(@"http://example.com/rss3",
                    [urls.RSSURLs[2] absoluteString]);
        completion_called = true;
      }));

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
    return completion_called;
  }));
}