blob: c510cf7d83e27a94ef71d9aafbdd74021abdd0fa [file] [log] [blame]
// 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.
#include "third_party/blink/renderer/core/dom/tree_ordered_map.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/html/html_div_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
namespace blink {
class TreeOrderedMapTest : public EditingTestBase {
protected:
void SetUp() override {
EditingTestBase::SetUp();
root_ = MakeGarbageCollected<HTMLDivElement>(GetDocument());
root_->setAttribute(html_names::kIdAttr, AtomicString("ROOT"));
GetDocument().body()->appendChild(root_);
}
Element* AddElement(AtomicString slot_name) {
auto* slot = MakeGarbageCollected<HTMLSlotElement>(GetDocument());
slot->setAttribute(html_names::kNameAttr, slot_name);
std::string id = "SLOT_" + base::NumberToString(++element_num);
slot->setAttribute(html_names::kIdAttr, AtomicString(id.c_str()));
root_->appendChild(slot);
return static_cast<Element*>(slot);
}
TreeScope& GetTreeScope() { return root_->GetTreeScope(); }
private:
int element_num{0};
Persistent<HTMLDivElement> root_;
};
TEST_F(TreeOrderedMapTest, Basic) {
auto* map = MakeGarbageCollected<TreeOrderedMap>();
AtomicString key("test");
auto& element = *AddElement(key);
map->Add(key, element);
EXPECT_TRUE(map->Contains(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), element);
map->Remove(key, element);
EXPECT_FALSE(map->Contains(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), nullptr);
}
TEST_F(TreeOrderedMapTest, DuplicateKeys) {
auto* map = MakeGarbageCollected<TreeOrderedMap>();
AtomicString key("test");
auto& element1 = *AddElement(key);
auto& element2 = *AddElement(key);
map->Add(key, element1);
EXPECT_TRUE(map->Contains(key));
EXPECT_FALSE(map->ContainsMultiple(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), element1);
map->Add(key, element2);
EXPECT_TRUE(map->Contains(key));
EXPECT_TRUE(map->ContainsMultiple(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), nullptr)
<< "No tree walk yet";
EXPECT_EQ(map->GetSlotByName(key, GetTreeScope()), element1);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), element1)
<< "Tree walk forced by GetSlotByName";
element1.remove(); // Remove it from the tree also.
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), element1)
<< "Make sure we don't touch the tree";
map->Remove(key, element1);
EXPECT_TRUE(map->Contains(key));
EXPECT_FALSE(map->ContainsMultiple(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), nullptr);
EXPECT_EQ(map->GetSlotByName(key, GetTreeScope()), element2);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), element2);
map->Remove(key, element2);
EXPECT_FALSE(map->Contains(key));
EXPECT_FALSE(map->ContainsMultiple(key));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key), nullptr);
EXPECT_EQ(map->GetSlotByName(key, GetTreeScope()), nullptr)
<< "nullptr even though we never removed element2 from the tree";
}
TEST_F(TreeOrderedMapTest, ManyKeys) {
auto* map = MakeGarbageCollected<TreeOrderedMap>();
AtomicString key1("test1");
AtomicString key2 = g_empty_atom; // Empty should be handled as a unique key
auto& element1 = *AddElement(key1);
auto& element2 = *AddElement(key1);
auto& element3 = *AddElement(key2);
auto& element4 = *AddElement(key2);
map->Add(key1, element1);
map->Add(key1, element2);
map->Add(key2, element3);
map->Add(key2, element4);
EXPECT_TRUE(map->Contains(key1));
EXPECT_TRUE(map->Contains(key2));
EXPECT_TRUE(map->ContainsMultiple(key1));
EXPECT_TRUE(map->ContainsMultiple(key2));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key1), nullptr);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key2), nullptr);
EXPECT_EQ(map->GetSlotByName(key1, GetTreeScope()), element1);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key1), element1);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key2), nullptr);
EXPECT_EQ(map->GetSlotByName(key2, GetTreeScope()), element3);
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key2), element3);
map->Remove(key1, element2);
map->Remove(key1, element1);
map->Remove(key2, element3);
element3.remove();
EXPECT_FALSE(map->Contains(key1));
EXPECT_TRUE(map->Contains(key2));
EXPECT_FALSE(map->ContainsMultiple(key2));
EXPECT_EQ(map->GetCachedFirstElementWithoutAccessingNodeTree(key2), nullptr);
EXPECT_EQ(map->GetSlotByName(key2, GetTreeScope()), element4);
}
TEST_F(TreeOrderedMapTest, RemovedDuplicateKeys) {
auto* map = MakeGarbageCollected<TreeOrderedMap>();
AtomicString key("test");
auto& outer = *AddElement(key);
auto& inner = *AddElement(key);
outer.appendChild(&inner);
map->Add(key, outer);
map->Add(key, inner);
EXPECT_EQ(map->GetSlotByName(key, GetTreeScope()), outer);
EXPECT_TRUE(map->ContainsMultiple(key));
outer.remove(); // This removes both elements from the tree
EXPECT_TRUE(map->ContainsMultiple(key)) << "We haven't touched the map yet";
TreeOrderedMap::RemoveScope tree_remove_scope;
map->Remove(key, outer);
EXPECT_TRUE(map->Contains(key))
<< "The map will still contain the entry for inner at this point";
EXPECT_FALSE(map->ContainsMultiple(key));
EXPECT_EQ(map->GetSlotByName(key, GetTreeScope()), nullptr);
EXPECT_FALSE(map->Contains(key))
<< "The call to GetSlotByName should have cleared the key entirely";
}
} // namespace blink