blob: eac8ded8ea23642446b96b024e18573bb519efc1 [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
#include "cobalt/dom/rule_matching.h"
#include <algorithm>
#include <vector>
#include "cobalt/css_parser/parser.h"
#include "cobalt/cssom/css_rule_list.h"
#include "cobalt/cssom/css_style_rule.h"
#include "cobalt/cssom/css_style_sheet.h"
#include "cobalt/dom/document.h"
#include "cobalt/dom/dom_stat_tracker.h"
#include "cobalt/dom/element.h"
#include "cobalt/dom/html_collection.h"
#include "cobalt/dom/html_element.h"
#include "cobalt/dom/html_element_context.h"
#include "cobalt/dom/node.h"
#include "cobalt/dom/node_descendants_iterator.h"
#include "cobalt/dom/node_list.h"
#include "cobalt/dom/testing/stub_window.h"
#include "cobalt/dom_parser/parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace dom {
class RuleMatchingTest : public ::testing::Test {
protected:
RuleMatchingTest()
: css_parser_(css_parser::Parser::Create()),
dom_parser_(new dom_parser::Parser()),
dom_stat_tracker_(new DomStatTracker("RuleMatchingTest")),
html_element_context_(NULL, css_parser_.get(), dom_parser_.get(), NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, dom_stat_tracker_.get(), "",
base::kApplicationStateStarted),
document_(new Document(&html_element_context_)),
root_(document_->CreateElement("html")->AsHTMLElement()) {
document_->AppendChild(root_);
}
~RuleMatchingTest() OVERRIDE {}
void UpdateAllMatchingRules();
scoped_ptr<css_parser::Parser> css_parser_;
scoped_ptr<dom_parser::Parser> dom_parser_;
scoped_ptr<DomStatTracker> dom_stat_tracker_;
HTMLElementContext html_element_context_;
scoped_refptr<Document> document_;
scoped_refptr<HTMLElement> root_;
scoped_refptr<cssom::CSSStyleSheet> css_style_sheet_;
};
void RuleMatchingTest::UpdateAllMatchingRules() {
document_->style_sheets()->Append(css_style_sheet_);
document_->UpdateSelectorTree();
NodeDescendantsIterator iterator(document_);
Node* child = iterator.First();
while (child) {
if (child->AsElement()) {
DCHECK(child->AsElement()->AsHTMLElement());
UpdateMatchingRules(child->AsElement()->AsHTMLElement());
}
child = iterator.Next();
}
}
// For each test, we go through the following process:
// 1. Generate rules in the style sheet from the CSS code.
// 2. Generate DOM elements from the HTML code.
// 3. Get the matching rules and check the result.
// * should match <div/>.
TEST_F(RuleMatchingTest, UniversalSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"* {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div should match <div/>.
TEST_F(RuleMatchingTest, TypeSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// [attr] should match <div attr/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchNoValue) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"[attr] {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div attr/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// [attr=value] should match <div attr="value"/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchEquals) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"[attr=value] {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div attr=\"value\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// [attr="value"] should match <div attr="value"/>.
TEST_F(RuleMatchingTest, AttributeSelectorMatchEqualsWithQuote) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"[attr=\"value\"] {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div attr=\"value\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// [attr=value] should not match <div attr/>.
TEST_F(RuleMatchingTest, AttributeSelectorNoMatchEquals) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"[attr=value] {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div attr/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// .my-class should match <div class="my-class"/>.
TEST_F(RuleMatchingTest, ClassSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
".my-class {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// #div1 should match <div id="div1"/>.
TEST_F(RuleMatchingTest, IdSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"#div1 {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div id=\"div1\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// :empty should match <div></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// :empty should match <div><!--comment--></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchCommentOnly) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><!--comment--></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// :empty shouldn't match <div> </div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassShouldMatchTextOnly) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div> </div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// div:focus should match focused div.
TEST_F(RuleMatchingTest, FocusPseudoClassMatch) {
// Give the document browsing context which is needed for focus to work.
testing::StubWindow window;
document_->set_window(window.window());
// Give the document initial computed style.
document_->SetViewport(math::Size(320, 240));
css_style_sheet_ = css_parser_->ParseStyleSheet(
":focus {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div tabIndex=\"-1\"/>");
root_->first_element_child()->AsHTMLElement()->Focus();
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div:focus shouldn't match unfocused div.
TEST_F(RuleMatchingTest, FocusPseudoClassNoMatch) {
// Give the document browsing context which is needed for focus to work.
testing::StubWindow window;
document_->set_window(window.window());
// Give the document initial computed style.
document_->SetViewport(math::Size(320, 240));
css_style_sheet_ = css_parser_->ParseStyleSheet(
":focus {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div tabIndex=\"-1\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// :not(.my-class) should match <div/>.
TEST_F(RuleMatchingTest, NotPseudoClassMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":not(.my-class) {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// :not(.my-class) shouldn't match <div class="my-class"/>.
TEST_F(RuleMatchingTest, NotPseudoClassNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":not(.my-class) {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// *:after should create and match the after pseudo element of all elements.
TEST_F(RuleMatchingTest, AfterPseudoElementMatchGlobal) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"*:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
html_element = root_->last_element_child()->AsHTMLElement();
matching_rules = html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div:after should create and match the after pseudo element of <div/>.
TEST_F(RuleMatchingTest, AfterPseudoElementSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kAfterPseudoElementType));
matching_rules =
html_element->pseudo_element(kAfterPseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div:after shouldn't create the after pseudo element of <span/>.
TEST_F(RuleMatchingTest, AfterPseudoElementSelectorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div:after {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<span/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
EXPECT_FALSE(html_element->pseudo_element(kAfterPseudoElementType));
}
// *:before should create and match the before pseudo element of all elements.
TEST_F(RuleMatchingTest, BeforePseudoElementMatchGlobal) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"*:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div class=\"a\"/><span class=\"a\"/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
html_element = root_->last_element_child()->AsHTMLElement();
matching_rules = html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div:before should create and match the before pseudo element of <div/>.
TEST_F(RuleMatchingTest, BeforePseudoElementSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
ASSERT_TRUE(html_element->pseudo_element(kBeforePseudoElementType));
matching_rules =
html_element->pseudo_element(kBeforePseudoElementType)->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div:before shouldn't create the before pseudo element of <span/>.
TEST_F(RuleMatchingTest, BeforePseudoElementSelectorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div:before {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<span/>");
UpdateAllMatchingRules();
HTMLElement* html_element = root_->first_element_child()->AsHTMLElement();
cssom::RulesWithCascadePrecedence* matching_rules =
html_element->matching_rules();
EXPECT_EQ(0, matching_rules->size());
EXPECT_FALSE(html_element->pseudo_element(kBeforePseudoElementType));
}
// :empty shouldn't match (the outer) <div><div></div></div>.
TEST_F(RuleMatchingTest, EmptyPseudoClassNotMatchElement) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
":empty {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><div></div></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// div.my-class should match <div class="my-class"/>.
TEST_F(RuleMatchingTest, CompoundSelectorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div.my-class {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// div.my-class shouldn't match <div/> or <span class="my-class"/>.
TEST_F(RuleMatchingTest, CompoundSelectorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div.my-class {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/><span class=\"my-class\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
matching_rules =
root_->last_element_child()->AsHTMLElement()->matching_rules();
EXPECT_EQ(0, matching_rules->size());
}
// "div span" should match inner span in <div><span><span></span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><span><span></span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()
->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// "span span" shouldn't match span in <div><span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorDescendantCombinatorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"span span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "div > span" should match span in <div><span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div > span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><span></span></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// "div > span" shouldn't match inner span in
// <div><span><span></span></span></div>.
TEST_F(RuleMatchingTest, ComplexSelectorChildCombinatorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div > span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div><span><span></span></span></div>");
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()
->first_element_child()
->first_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "span + span" should match second span in <span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"span + span {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// "span + span" shouldm't match first span in <span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorNextSiblingCombinatorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"span + span {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
// "div ~ span" should match second span in <div/><span/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div ~ span {}", base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/><span/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
// "span ~ span" shouldn't match span in <div/><span/>.
TEST_F(RuleMatchingTest, ComplexSelectorFollowingSiblingCombinatorNoMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"span ~ span {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html("<div/><span/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->last_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(0, matching_rules->size());
}
TEST_F(RuleMatchingTest, SelectorListMatchShouldContainAllMatches) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
".first-class, #my-id, .first-class.second-class {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html(
"<div class=\"first-class second-class\" id=\"my-id\"/>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->first_element_child()->AsHTMLElement()->matching_rules();
ASSERT_EQ(3, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[1].first);
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[2].first);
std::vector<cssom::Specificity> vs;
vs.push_back((*matching_rules)[0].second.specificity());
vs.push_back((*matching_rules)[1].second.specificity());
vs.push_back((*matching_rules)[2].second.specificity());
sort(vs.begin(), vs.end());
EXPECT_EQ(vs[0], cssom::Specificity(0, 1, 0));
EXPECT_EQ(vs[1], cssom::Specificity(0, 2, 0));
EXPECT_EQ(vs[2], cssom::Specificity(1, 0, 0));
}
// A complex example using several combinators.
TEST_F(RuleMatchingTest, ComplexSelectorCombinedMatch) {
css_style_sheet_ = css_parser_->ParseStyleSheet(
"div ~ span + div ~ div + div > div + div {}",
base::SourceLocation("[object RuleMatchingTest]", 1, 1));
root_->set_inner_html(
"<div/><span/><span/><div/><span/><div/>"
"<div><div/><div/></div>");
UpdateAllMatchingRules();
cssom::RulesWithCascadePrecedence* matching_rules =
root_->last_element_child()
->last_element_child()
->AsHTMLElement()
->matching_rules();
ASSERT_EQ(1, matching_rules->size());
EXPECT_EQ(css_style_sheet_->css_rules()->Item(0), (*matching_rules)[0].first);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldReturnFirstMatch) {
root_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "div", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
EXPECT_FALSE(QuerySelector(document_, "span", css_parser_.get()));
}
TEST_F(RuleMatchingTest, QuerySelectorShouldLimitResultInSubtree) {
root_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "div", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
scoped_refptr<Element> div2 = QuerySelector(div1, "div", css_parser_.get());
ASSERT_TRUE(div2);
EXPECT_EQ("div2", div2->id());
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorOutsideSubtree) {
root_->set_inner_html(
"<div class='out'>"
" <div id='div1'>"
" <span/>"
" </div>"
"</div>");
scoped_refptr<Element> div1 =
QuerySelector(document_, "#div1", css_parser_.get());
ASSERT_TRUE(div1);
EXPECT_EQ("div1", div1->id());
scoped_refptr<Element> span =
QuerySelector(div1, ".out span", css_parser_.get());
EXPECT_TRUE(span);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsRecursively) {
root_->set_inner_html(
"<div/>"
"<div/>"
"<p/>"
"<div/>"
"<span/>");
scoped_refptr<Element> span =
QuerySelector(document_, "div + div ~ span", css_parser_.get());
EXPECT_TRUE(span);
}
TEST_F(RuleMatchingTest, QuerySelectorShouldMatchCombinatorsCombined) {
root_->set_inner_html(
"<div/>"
"<span/>"
"<span/>"
"<div/>"
"<span/>"
"<div/>"
"<div>"
" <div/>"
" <div/>"
"</div>");
scoped_refptr<Element> div = QuerySelector(
document_, "div ~ span + div ~ div + div > div + div", css_parser_.get());
EXPECT_TRUE(div);
}
TEST_F(RuleMatchingTest, QuerySelectorAllShouldReturnAllMatches) {
root_->set_inner_html(
"<div id='div1'>"
" <div id='div2'/>"
" <div id='div3'/>"
"</div>");
scoped_refptr<NodeList> node_list;
node_list = QuerySelectorAll(document_, "div", css_parser_.get());
ASSERT_EQ(3, node_list->length());
EXPECT_EQ("div1", node_list->Item(0)->AsElement()->id());
EXPECT_EQ("div2", node_list->Item(1)->AsElement()->id());
EXPECT_EQ("div3", node_list->Item(2)->AsElement()->id());
node_list = QuerySelectorAll(document_, "span", css_parser_.get());
EXPECT_EQ(0, node_list->length());
}
} // namespace dom
} // namespace cobalt