text: // Copyright 2016 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
  
  #include <sstream>
  #include "third_party/blink/renderer/core/dom/range.h"
  #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
  
  namespace blink {
  
  class EphemeralRangeTest : public EditingTestBase {
   protected:
    template <typename Traversal = NodeTraversal>
    std::string TraverseRange(Range*) const;
  
    template <typename Strategy>
    std::string TraverseRange(const EphemeralRangeTemplate<Strategy>&) const;
  
    Range* GetBodyRange() const;
  };
  
  template <typename Traversal>
  std::string EphemeralRangeTest::TraverseRange(Range* range) const {
    std::stringstream nodes_content;
    for (Node* node = range->FirstNode(); node != range->PastLastNode();
         node = Traversal::Next(*node)) {
      nodes_content << "[" << *node << "]";
    }
  
    return nodes_content.str();
  }
  
  template <typename Strategy>
  std::string EphemeralRangeTest::TraverseRange(
      const EphemeralRangeTemplate<Strategy>& range) const {
    std::stringstream nodes_content;
    for (const Node& node : range.Nodes())
      nodes_content << "[" << node << "]";
  
    return nodes_content.str();
  }
  
  Range* EphemeralRangeTest::GetBodyRange() const {
    Range* range = Range::Create(GetDocument());
    range->selectNode(GetDocument().body());
    return range;
  }
  
  // Tests that |EphemeralRange::nodes()| will traverse the whole range exactly as
  // |for (Node* n = firstNode(); n != pastLastNode(); n = Traversal::next(*n))|
  // does.
  TEST_F(EphemeralRangeTest, rangeTraversalDOM) {
    const char* body_content =
        "<p id='host'>"
        "<b id='zero'>0</b>"
        "<b id='one'>1</b>"
        "<b id='two'>22</b>"
        "<span id='three'>333</span>"
        "</p>";
    SetBodyContent(body_content);
  
    const std::string expected_nodes(
        "[BODY][P id=\"host\"][B id=\"zero\"][#text \"0\"][B id=\"one\"][#text "
        "\"1\"][B id=\"two\"][#text \"22\"][SPAN id=\"three\"][#text \"333\"]");
  
    // Check two ways to traverse.
    EXPECT_EQ(expected_nodes, TraverseRange<>(GetBodyRange()));
    EXPECT_EQ(TraverseRange<>(GetBodyRange()),
              TraverseRange(EphemeralRange(GetBodyRange())));
  
    EXPECT_EQ(expected_nodes, TraverseRange<FlatTreeTraversal>(GetBodyRange()));
    EXPECT_EQ(TraverseRange<FlatTreeTraversal>(GetBodyRange()),
              TraverseRange(EphemeralRangeInFlatTree(GetBodyRange())));
  }
  
  // Tests that |inRange| helper will traverse the whole range with shadow DOM.
  TEST_F(EphemeralRangeTest, rangeShadowTraversal) {
    const char* body_content =
        "<b id='zero'>0</b>"
        "<p id='host'>"
        "<b id='one'>1</b>"
        "<b id='two'>22</b>"
        "<b id='three'>333</b>"
        "</p>"
        "<b id='four'>4444</b>";
    const char* shadow_content =
        "<p id='five'>55555</p>"
        "<content select=#two></content>"
        "<content select=#one></content>"
        "<span id='six'>666666</span>"
        "<p id='seven'>7777777</p>";
    SetBodyContent(body_content);
    SetShadowContent(shadow_content, "host");
  
    const std::string expected_nodes(
        "[BODY][B id=\"zero\"][#text \"0\"][P id=\"host\"][P id=\"five\"][#text "
        "\"55555\"][B id=\"two\"][#text \"22\"][B id=\"one\"][#text \"1\"][SPAN "
        "id=\"six\"][#text \"666666\"][P id=\"seven\"][#text \"7777777\"][B "
        "id=\"four\"][#text \"4444\"]");
  
    EXPECT_EQ(expected_nodes, TraverseRange<FlatTreeTraversal>(GetBodyRange()));
    EXPECT_EQ(TraverseRange<FlatTreeTraversal>(GetBodyRange()),
              TraverseRange(EphemeralRangeInFlatTree(GetBodyRange())));
    // Node 'three' should not appear in FlatTreeTraversal.
    EXPECT_EQ(expected_nodes.find("three") == std::string::npos, true);
  }
  
  // Limit a range and check that it will be traversed correctly.
  TEST_F(EphemeralRangeTest, rangeTraversalLimitedDOM) {
    const char* body_content =
        "<p id='host'>"
        "<b id='zero'>0</b>"
        "<b id='one'>1</b>"
        "<b id='two'>22</b>"
        "<span id='three'>333</span>"
        "</p>";
    SetBodyContent(body_content);
  
    Range* until_b = GetBodyRange();
    until_b->setEnd(GetDocument().getElementById("one"), 0,
                    IGNORE_EXCEPTION_FOR_TESTING);
    EXPECT_EQ("[BODY][P id=\"host\"][B id=\"zero\"][#text \"0\"][B id=\"one\"]",
              TraverseRange<>(until_b));
    EXPECT_EQ(TraverseRange<>(until_b), TraverseRange(EphemeralRange(until_b)));
  
    Range* from_b_to_span = GetBodyRange();
    from_b_to_span->setStart(GetDocument().getElementById("one"), 0,
                             IGNORE_EXCEPTION_FOR_TESTING);
    from_b_to_span->setEnd(GetDocument().getElementById("three"), 0,
                           IGNORE_EXCEPTION_FOR_TESTING);
    EXPECT_EQ("[#text \"1\"][B id=\"two\"][#text \"22\"][SPAN id=\"three\"]",
              TraverseRange<>(from_b_to_span));
    EXPECT_EQ(TraverseRange<>(from_b_to_span),
              TraverseRange(EphemeralRange(from_b_to_span)));
  }
  
  TEST_F(EphemeralRangeTest, rangeTraversalLimitedFlatTree) {
    const char* body_content =
        "<b id='zero'>0</b>"
        "<p id='host'>"
        "<b id='one'>1</b>"
        "<b id='two'>22</b>"
        "</p>"
        "<b id='three'>333</b>";
    const char* shadow_content =
        "<p id='four'>4444</p>"
        "<content select=#two></content>"
        "<content select=#one></content>"
        "<span id='five'>55555</span>"
        "<p id='six'>666666</p>";
    SetBodyContent(body_content);
    ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  
    const PositionInFlatTree start_position(GetDocument().getElementById("one"),
                                            0);
    const PositionInFlatTree limit_position(shadow_root->getElementById("five"),
                                            0);
    const PositionInFlatTree end_position(shadow_root->getElementById("six"), 0);
    const EphemeralRangeInFlatTree from_b_to_span(start_position, limit_position);
    EXPECT_EQ("[#text \"1\"][SPAN id=\"five\"]", TraverseRange(from_b_to_span));
  
    const EphemeralRangeInFlatTree from_span_to_end(limit_position, end_position);
    EXPECT_EQ("[#text \"55555\"][P id=\"six\"]", TraverseRange(from_span_to_end));
  }
  
  TEST_F(EphemeralRangeTest, traversalEmptyRanges) {
    const char* body_content =
        "<p id='host'>"
        "<b id='one'>1</b>"
        "</p>";
    SetBodyContent(body_content);
  
    // Expect no iterations in loop for an empty EphemeralRange.
    EXPECT_EQ(std::string(), TraverseRange(EphemeralRange()));
  
    auto iterable = EphemeralRange().Nodes();
    // Tree iterators have only |operator !=| ATM.
    EXPECT_FALSE(iterable.begin() != iterable.end());
  
    const EphemeralRange single_position_range(GetBodyRange()->StartPosition());
    EXPECT_FALSE(single_position_range.IsNull());
    EXPECT_EQ(std::string(), TraverseRange(single_position_range));
    EXPECT_EQ(single_position_range.StartPosition().NodeAsRangeFirstNode(),
              single_position_range.EndPosition().NodeAsRangePastLastNode());
  }
  
  TEST_F(EphemeralRangeTest, commonAncesstorDOM) {
    const char* body_content =
        "<p id='host'>00"
        "<b id='one'>11</b>"
        "<b id='two'>22</b>"
        "<b id='three'>33</b>"
        "</p>";
    SetBodyContent(body_content);
  
    const Position start_position(GetDocument().getElementById("one"), 0);
    const Position end_position(GetDocument().getElementById("two"), 0);
    const EphemeralRange range(start_position, end_position);
    EXPECT_EQ(GetDocument().getElementById("host"),
              range.CommonAncestorContainer());
  }
  
  TEST_F(EphemeralRangeTest, commonAncesstorFlatTree) {
    const char* body_content =
        "<b id='zero'>0</b>"
        "<p id='host'>"
        "<b id='one'>1</b>"
        "<b id='two'>22</b>"
        "</p>"
        "<b id='three'>333</b>";
    const char* shadow_content =
        "<p id='four'>4444</p>"
        "<content select=#two></content>"
        "<content select=#one></content>"
        "<p id='five'>55555</p>";
    SetBodyContent(body_content);
    ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  
    const PositionInFlatTree start_position(GetDocument().getElementById("one"),
                                            0);
    const PositionInFlatTree end_position(shadow_root->getElementById("five"), 0);
    const EphemeralRangeInFlatTree range(start_position, end_position);
    EXPECT_EQ(GetDocument().getElementById("host"),
              range.CommonAncestorContainer());
  }
  
  }  // namespace blink
  