text: /*
   * Copyright 2013 Google Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  // Author: bharathbhushan@google.com (Bharath Bhushan Kowshik Raghupathi)
  //
  // Test code for SplitHtmlHelper filter.
  
  #include "net/instaweb/rewriter/public/split_html_helper_filter.h"
  
  #include "net/instaweb/http/public/log_record.h"
  #include "net/instaweb/http/public/logging_proto_impl.h"
  #include "net/instaweb/http/public/request_context.h"
  #include "net/instaweb/rewriter/public/critical_images_finder.h"
  #include "net/instaweb/rewriter/public/critical_images_finder_test_base.h"
  #include "net/instaweb/rewriter/public/delay_images_filter.h"
  #include "net/instaweb/rewriter/public/lazyload_images_filter.h"
  #include "net/instaweb/rewriter/public/rewrite_driver.h"
  #include "net/instaweb/rewriter/public/rewrite_options.h"
  #include "net/instaweb/rewriter/public/rewrite_test_base.h"
  #include "net/instaweb/rewriter/public/server_context.h"
  #include "pagespeed/kernel/base/gtest.h"
  #include "pagespeed/kernel/base/ref_counted_ptr.h"
  #include "pagespeed/kernel/base/string_util.h"
  #include "pagespeed/kernel/base/wildcard.h"  // for Wildcard
  #include "pagespeed/kernel/http/content_type.h"
  #include "pagespeed/kernel/http/http_names.h"
  #include "pagespeed/kernel/http/request_headers.h"
  #include "pagespeed/kernel/http/user_agent_matcher_test_base.h"
  #include "pagespeed/opt/logging/enums.pb.h"
  
  namespace net_instaweb {
  
  namespace {
  const char kSampleJpegData[] = "data:image/jpeg;base64*";
  }  // namespace
  
  class SplitHtmlHelperFilterTest : public RewriteTestBase {
   public:
    SplitHtmlHelperFilterTest() {}
  
   protected:
    virtual void SetUp() {
      RewriteTestBase::SetUp();
      SetCurrentUserAgent(
          UserAgentMatcherTestBase::kChrome18UserAgent);
      SetHtmlMimetype();  // Prevent insertion of CDATA tags to static JS.
    }
  
    void Init() {
      options()->EnableFilter(RewriteOptions::kSplitHtmlHelper);
      options()->set_critical_line_config("div[@id=\"b\"],"
                                          "div[@id=\"c\"]");
      rewrite_driver()->AddFilters();
      rewrite_driver()->set_critical_images_info(new CriticalImagesInfo);
    }
  
    void CheckNumCriticalImages(int expected) {
      CriticalImagesInfo* critical_images_info =
          rewrite_driver()->critical_images_info();
      EXPECT_EQ(expected, critical_images_info->html_critical_images.size());
    }
  
    void CheckCriticalImage(StringPiece url) {
      CriticalImagesFinder* finder =
          rewrite_driver()->server_context()->critical_images_finder();
      EXPECT_TRUE(finder->IsHtmlCriticalImage(url, rewrite_driver()));
    }
  
    void CheckLoggingStatus(RewriterHtmlApplication::Status status) {
      rewrite_driver()->log_record()->WriteLog();
      for (int i = 0; i < logging_info()->rewriter_stats_size(); i++) {
        if (logging_info()->rewriter_stats(i).id() == "se" &&
            logging_info()->rewriter_stats(i).has_html_status()) {
          EXPECT_EQ(status, logging_info()->rewriter_stats(i).html_status());
          return;
        }
      }
      FAIL();
    }
  
    void SetBtfRequest() {
      rewrite_driver()->request_context()->set_split_request_type(
          RequestContext::SPLIT_BELOW_THE_FOLD);
    }
  
    void SetAtfRequest() {
      rewrite_driver()->request_context()->set_split_request_type(
          RequestContext::SPLIT_ABOVE_THE_FOLD);
    }
  
    GoogleString GetLazyloadImageTag(const GoogleString& url, bool no_transform) {
      return StrCat("<img data-pagespeed-lazy-src='", url, "'",
          no_transform ? " data-pagespeed-no-transform" : "",
          " src=\"/psajs/1.0.gif\" "
          "onload=\"", LazyloadImagesFilter::kImageOnloadCode,
          "\" onerror=\"this.onerror=null;",
          LazyloadImagesFilter::kImageOnloadCode, "\">");
    }
  
    GoogleString GetInlinePreviewImageTag(const GoogleString& url,
                                          const GoogleString& low_res_src) {
      return StrCat(
          "<img data-pagespeed-high-res-src=\"", url, "\" src=\"", low_res_src,
          "\" onload=\"", DelayImagesFilter::kImageOnloadCode,
          "\" onerror=\"this.onerror=null;", DelayImagesFilter::kImageOnloadCode,
          "\">");
    }
  
    GoogleString GetImageOnloadScriptBlock() const {
      return StrCat("<script data-pagespeed-no-defer type=\"text/javascript\">",
                    DelayImagesFilter::kImageOnloadJsSnippet,
                    "</script>");
    }
  
    RequestHeaders request_headers_;
  };
  
  TEST_F(SplitHtmlHelperFilterTest, BasicTest) {
    Init();
    EXPECT_TRUE(options()->Enabled(RewriteOptions::kSplitHtmlHelper));
    ValidateNoChanges("split_helper_basic_test", "");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, DisabledTest1) {
    Init();
    EXPECT_TRUE(options()->Enabled(RewriteOptions::kSplitHtmlHelper));
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("");
    ValidateNoChanges("split_helper_disabled1", "");
    CheckLoggingStatus(RewriterHtmlApplication::DISABLED);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, DisabledTest2) {
    Init();
    EXPECT_TRUE(options()->Enabled(RewriteOptions::kSplitHtmlHelper));
    SetCurrentUserAgent("does_not_support_split");
    ValidateNoChanges("split_helper_disabled2", "");
    CheckLoggingStatus(RewriterHtmlApplication::USER_AGENT_NOT_SUPPORTED);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequest) {
    Init();
    ValidateExpected(
        "split_helper_atf",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</body>");
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, ExplicitAtfRequest) {
    Init();
    SetAtfRequest();
    ValidateExpected(
        "split_helper_explicit_atf",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</body>");
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestWithCriticalImages) {
    Init();
    CriticalImagesInfo* critical_images_info = new CriticalImagesInfo;
    critical_images_info->html_critical_images.insert("http://test.com/4.jpeg");
    critical_images_info->html_critical_images.insert("http://test.com/5.jpeg");
    rewrite_driver()->set_critical_images_info(critical_images_info);
    CheckNumCriticalImages(2);
  
    ValidateExpected(
        "split_helper_atf",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</body>");
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestWithNullCriticalImages) {
    Init();
    rewrite_driver()->set_critical_images_info(NULL);
    ValidateExpected(
        "split_helper_atf_with_null_critical_images",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</body>");
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, BtfRequest) {
    Init();
    SetBtfRequest();
    CriticalImagesInfo* critical_images_info = new CriticalImagesInfo;
    critical_images_info->html_critical_images.insert("http://test.com/1.jpeg");
    rewrite_driver()->set_critical_images_info(critical_images_info);
    CheckNumCriticalImages(1);
    ValidateNoChanges(
        "split_helper_btf",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(0);
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestTwoXpaths) {
    Init();
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("div[@id=\"b\"]:div[@id=\"d\"]");
  
    ValidateExpected(
        "split_helper_atf_2xpaths",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'>"
          "<div id='c'><img src='3.jpeg'></div>"
        "</div>"
        "<div id='d'><img src='4.jpeg'></div>"
        "</body>",
  
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform>"
          "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</div>"
        "<div id='d'><img src='4.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(2);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckCriticalImage("http://test.com/4.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestXPathWithChildCount) {
    Init();
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("div[2]:div[4]");
  
    ValidateExpected(
        "split_helper_atf_xpath_with_child_count",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "<div id='d'><img src='4.jpeg'></div>"
        "</body>",
  
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "<div id='d'><img src='4.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(2);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckCriticalImage("http://test.com/4.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestNoDeferCases) {
    Init();
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("div[2]:div[4]");
  
    ValidateExpected(
        "split_helper_atf_nodefer_cases",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<script data-pagespeed-no-defer></script>"
        "<div id='b'><img src='2.jpeg'>"
          "<script data-pagespeed-no-defer></script>"
          "<div id='c' data-pagespeed-no-defer></div>"
        "</div>"
        "<div id='d'><img src='3.jpeg'></div>"
        "<div id='e'><img src='4.jpeg'></div>"
        "</body>",
  
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<script data-pagespeed-no-defer></script>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform>"
          "<script data-pagespeed-no-defer></script>"
          "<div id='c' data-pagespeed-no-defer></div>"
        "</div>"
        "<div id='d'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "<div id='e'><img src='4.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(2);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckCriticalImage("http://test.com/4.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestNonCountedChildren) {
    Init();
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("div[2]");
  
    ValidateExpected(
        "split_helper_atf_nodefer_cases",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<link>"
        "<script></script>"
        "<noscript></noscript>"
        "<style></style>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
  
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<link>"
        "<script></script>"
        "<noscript></noscript>"
        "<style></style>"
        "<div id='b'><img src='2.jpeg' data-pagespeed-no-transform></div>"
        "<div id='c'><img src='3.jpeg' data-pagespeed-no-transform></div>"
        "</body>");
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, BtfRequestConfigInHeader) {
    Init();
    SetBtfRequest();
    options()->ClearSignatureForTesting();
    options()->set_critical_line_config("");
    request_headers_.Add(HttpAttributes::kXPsaSplitConfig, "div[2]");
    rewrite_driver()->SetRequestHeaders(request_headers_);
  
    ValidateNoChanges(
        "split_helper_btf_request_config_in_header",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(0);
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestWithLazyload) {
    TestCriticalImagesFinder* finder =
        new TestCriticalImagesFinder(NULL, statistics());
    server_context()->set_critical_images_finder(finder);
    options()->EnableFilter(RewriteOptions::kLazyloadImages);
    options()->set_support_noscript_enabled(false);
    Init();
    ValidateExpected(
        "split_helper_atf_with_lazyload",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
  
        StrCat(
            GetLazyloadScriptHtml(),
            "<body>"
            "<div id='a'><img src='1.jpeg'></div>"
            "<div id='b'>",
              GetLazyloadImageTag("2.jpeg", true),
            "</div>",
            "<div id='c'>", GetLazyloadImageTag("3.jpeg", true), "</div>",
            GetLazyloadPostscriptHtml(),
            "</body>"));
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, BtfRequestWithLazyload) {
    TestCriticalImagesFinder* finder =
        new TestCriticalImagesFinder(NULL, statistics());
    server_context()->set_critical_images_finder(finder);
    options()->EnableFilter(RewriteOptions::kLazyloadImages);
    options()->set_support_noscript_enabled(false);
    Init();
    SetBtfRequest();
    GoogleString expected_output_html = StrCat(
        GetLazyloadScriptHtml(),
        "<body>"
        "<div id='a'>",
          GetLazyloadImageTag("1.jpeg", false),
        "</div>"
        "<div id='b'>", GetLazyloadImageTag("2.jpeg", false), "</div>"
        "<div id='c'>", GetLazyloadImageTag("3.jpeg", false), "</div>",
        GetLazyloadPostscriptHtml(),
        "</body>");
    ValidateExpected(
        "split_helper_btf_with_lazyload",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        expected_output_html);
  
    CheckNumCriticalImages(0);
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfRequestWithInlinePreview) {
    TestCriticalImagesFinder* finder =
        new TestCriticalImagesFinder(NULL, statistics());
    server_context()->set_critical_images_finder(finder);
    options()->EnableFilter(RewriteOptions::kDelayImages);
    options()->set_support_noscript_enabled(false);
    Init();
    AddFileToMockFetcher("http://test.com/1.jpeg", "Sample.jpg",
                         kContentTypeJpeg, 100);
  
    GoogleString input_html = "<body>"
        "<div id='a'><img src=\"1.jpeg\"></div>"
        "<div id='b'><img src=\"2.jpeg\"></div>"
        "<div id='c'><img src=\"3.jpeg\"></div>"
        "</body>";
    GoogleString output_html = StrCat(
        "<html>\n<body>"
        "<div id='a'>",
        GetImageOnloadScriptBlock(),
          GetInlinePreviewImageTag("1.jpeg", kSampleJpegData),
        "</div>"
        "<div id='b'><img src=\"2.jpeg\"></div>"
        "<div id='c'><img src=\"3.jpeg\"></div>"
        "</body></html>");
    Parse("split_helper_atf_with_inline_preview", input_html);
    EXPECT_TRUE(Wildcard(output_html).Match(output_buffer_))
        << "Expected:\n" << output_html << "\n\nGot:\n" << output_buffer_;
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, BtfRequestWithInlinePreview) {
    TestCriticalImagesFinder* finder =
        new TestCriticalImagesFinder(NULL, statistics());
    server_context()->set_critical_images_finder(finder);
    options()->EnableFilter(RewriteOptions::kDelayImages);
    options()->set_support_noscript_enabled(false);
    Init();
    SetBtfRequest();
    AddFileToMockFetcher("http://test.com/1.jpeg", "Sample.jpg",
                         kContentTypeJpeg, 100);
    AddFileToMockFetcher("http://test.com/2.jpeg", "Sample.jpg",
                         kContentTypeJpeg, 100);
    AddFileToMockFetcher("http://test.com/3.jpeg", "Sample.jpg",
                         kContentTypeJpeg, 100);
    ValidateExpected(
        "split_helper_atf",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>",
        "<body>"
        "<div id='a'><img src='1.jpeg'></div>"
        "<div id='b'><img src='2.jpeg'></div>"
        "<div id='c'><img src='3.jpeg'></div>"
        "</body>");
    CheckNumCriticalImages(0);
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  TEST_F(SplitHtmlHelperFilterTest, AtfNestedPanelsRequestWithInlinePreview) {
    TestCriticalImagesFinder* finder =
        new TestCriticalImagesFinder(NULL, statistics());
    server_context()->set_critical_images_finder(finder);
    options()->EnableFilter(RewriteOptions::kDelayImages);
    options()->set_support_noscript_enabled(false);
    Init();
    AddFileToMockFetcher("http://test.com/1.jpeg", "Sample.jpg",
                         kContentTypeJpeg, 100);
  
    GoogleString input_html = "<body>"
        "<div id='a'><img src=\"1.jpeg\"></div>"
        "<div id='b'>"
          "<div id='c'></div>"
          "<img src=\"2.jpeg\"></div>"
        "<div id='d'><img src=\"3.jpeg\"></div>"
        "</body>";
    GoogleString output_html = StrCat(
        "<html>\n<body>"
        "<div id='a'>",
        GetImageOnloadScriptBlock(),
          GetInlinePreviewImageTag("1.jpeg", kSampleJpegData),
        "</div>"
        "<div id='b'>"
          "<div id='c'></div>"
          "<img src=\"2.jpeg\"></div>"
        "<div id='d'><img src=\"3.jpeg\"></div>"
        "</body></html>");
    Parse("split_helper_atf_with_inline_preview", input_html);
    EXPECT_TRUE(Wildcard(output_html).Match(output_buffer_))
        << "Expected:\n" << output_html << "\n\nGot:\n" << output_buffer_;
    CheckNumCriticalImages(1);
    CheckCriticalImage("http://test.com/1.jpeg");
    CheckLoggingStatus(RewriterHtmlApplication::ACTIVE);
  }
  
  }  // namespace net_instaweb
  