blob: 3429edc09ad50065af09d8dd96d5f10a14c2dc87 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. 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/cssom/serializer.h"
#include "cobalt/base/token.h"
#include "cobalt/css_parser/parser.h"
#include "cobalt/cssom/css_style_rule.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
namespace cssom {
namespace {
struct TestPair {
// Non-normalized source: the expected serialized output differs.
TestPair(const char* serialized, const char* source)
: serialized(serialized), source(source) {}
// Normalized source: the expected serialized output is identical.
TestPair(const char* normalized) // NOLINT(runtime/explicit)
: serialized(normalized), source(normalized) {}
const char* serialized;
const char* source;
};
} // namespace
TEST(SerializerTest, SerializeSelectorsTest) {
// clang-format off
const TestPair selectors[] = {
// Simple selectors
"*",
"tag",
"[attr]",
"[attr=\"value\"]",
"[attr~=\"value\"]",
"[attr|=\"value\"]",
"[attr^=\"value\"]",
"[attr$=\"value\"]",
"[attr*=\"value\"]",
".class",
"#id",
":active",
":empty",
":focus",
":hover",
":not(tag)",
"::before",
"::after",
// Compound selectors
"tag[attr=\"value\"]",
"tag.class",
"tag#id",
"tag:hover",
"tag::after",
"[attr].class",
"[attr=\"value\"]#id",
"[attr~=\"value\"]:active",
"[attr|=\"value\"]::after",
".class#id",
".class:empty",
".class::before",
"#id:hover",
"#id::after",
":focus::before",
"tag[attr=\"value\"].class",
"tag[attr^=\"value\"].class#id",
"tag[attr$=\"value\"].class#id:active",
"tag[attr*=\"value\"].class#id:active::before",
// Universal selector is dropped from a compound selector.
{"tag", "*tag"},
{"[attr=\"value\"]", "*[attr=\"value\"]"},
{".class", "*.class"},
{"#id", "*#id"},
{":focus", "*:focus"},
{"::before", "*::before"},
{"tag[attr=\"value\"].class#id:empty::before",
"*tag[attr=\"value\"].class#id:empty::before"},
// Old syntax pseudo-element with one ':' is normalized to use '::'.
{"div::before", "div:before"},
{"div::after", "div:after"},
// Compound classes/ids/attributes are sorted, and no whitespace in attrs.
{".aa.bb.cc.dd.ee.ff.gg.hh.ii", ".ff.dd.aa.bb.gg.hh.ee.ii.cc"},
{"#aa#bb#cc#dd#ee#ff#gg#hh#ii", "#ff#dd#aa#bb#gg#hh#ee#ii#cc"},
{"[a=\"x\"][b][c=\"z\"][d=\"w\"][e=\"y\"]",
"[ d = w ][a =x][e= y][ c=z][ b ]"},
// Whitespace is normalized around combinators.
{"div img", "div img"},
{"div > img", "div > img"},
{"div + img", "div + img"},
{"div ~ img", "div ~ img"},
{"div > img", "div>img"},
{"div + img", "div+img"},
{"div ~ img", "div~img"},
{"div img", "div img "},
{"div + img > .class", "div +img> .class"},
{"[attr^=\"value\"] + div", "[attr ^= value] +div "},
{"div:empty ~ [attr=\"value\"]", "div:empty~[attr = value]"},
// Tags/classes/ids with a descendant combinator are not sorted.
"tag1 tag2",
"tag2 tag1",
".class1 .class2",
".class2 .class1",
"#id1 #id2",
"#id2 #id1",
// Type is sorted first, then text of same-typed simple selectors.
{"I[C^=\"y\"][G=\"x\"][J].B.E.H#A#D#F", "[J]I.H[G=x]#F.E#D[C^=y].B#A"},
// Pseudo-classes are sorted, :not() is sorted by its argument, and all
// pseudo-classes are sorted before (':' or '::') pseudo-elements.
{":active:empty:focus:hover:not(aa):not(bb):not(cc)::after::before",
":not(bb):empty::before:focus:not(aa):after:active:hover:not(cc)"},
// Different types of selectors can be in a comma-separated group.
"tag, :not(.class), outer inner, parent > child, [attr=\"value\"], #id",
// The the order of the group is not rearranged.
".classA.classB, tag:hover",
"tag:hover, .classA.classB",
// Spacing of a comma-separated group is normalized, but it's not sorted.
{"pig, cow, horse, sheep, goat", " pig,cow ,horse , sheep, goat "},
// Each compound selector in a group is individually sorted.
{".cow.pig, .horse, .goat.sheep", ".pig.cow, .horse, .sheep.goat"},
// Unnecessarily escaped identifier is normalized.
{"X_y-1", "\\X\\_\\y\\-\\31"},
// Attribute values are quoted.
{"[quotes=\"value\"]", "[quotes=value]"},
// Attributes with un-escaped string values.
"[empty=\"\"]",
"[alpha=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"]",
"[numeric=\"0123456789\"]",
"[ascii=\" !#$%&'()*+,-./:;<=>?@[]^_`{|}~\"]",
u8"[unicode=\"2-\u00a3 3-\u1d01 4-\U0002070e\"]",
// Attributes with escaped string values.
"[low=\"\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f \"]",
"[low=\"\\10 \\11 \\12 \\13 \\14 \\15 \\16 \\17 \"]",
"[low=\"\\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f \"]",
{"[quote_backslash=\"-\\\"-\\\\-\"]",
"[quote_backslash=\"-\\22 -\\5c -\"]"},
};
// clang-format on
base::Token::ScopedAlphabeticalSorting sort_scope;
std::unique_ptr<css_parser::Parser> css_parser = css_parser::Parser::Create();
base::SourceLocation loc("[object SelectorTest]", 1, 1);
for (const TestPair& selector : selectors) {
scoped_refptr<CSSStyleRule> css_style_rule =
css_parser->ParseRule(std::string(selector.source) + " {}", loc)
->AsCSSStyleRule();
EXPECT_EQ(selector.serialized, css_style_rule->selector_text())
<< " Source: \"" << selector.source << '"';
}
}
TEST(SerializerTest, SerializeIdentifierTest) {
// clang-format off
const TestPair identifiers[] = {
// Non-escaped ASCII.
"ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-012346789",
// Escaped ASCII.
{"low\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f ",
"low\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"},
{"low\\10 \\11 \\12 \\13 \\14 \\15 \\16 \\17 ",
"low\x10\x11\x12\x13\x14\x15\x16\x17"},
{"low\\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f ",
"low\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"},
{"del\\7f ",
"del\x7f"},
{"vis\\ \\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/",
"vis !\"#$%&'()*+,./"},
{"vis\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\`\\{\\|\\}\\~",
"vis:;<=>?@[\\]^`{|}~"},
// Initial hyphen is escaped if it's the only character.
{"\\-", "-"},
// Initial hyphen is not escaped if there's more after it.
"-xyz",
// Leading numeric is escaped, with or without initial hyphen.
{"\\30 0", "00"},
{"\\31 0", "10"},
{"\\32 0", "20"},
{"\\33 0", "30"},
{"\\34 0", "40"},
{"\\35 0", "50"},
{"\\36 0", "60"},
{"\\37 0", "70"},
{"\\38 0", "80"},
{"\\39 0", "90"},
{"-\\30 0", "-00"},
{"-\\31 0", "-10"},
{"-\\32 0", "-20"},
{"-\\33 0", "-30"},
{"-\\34 0", "-40"},
{"-\\35 0", "-50"},
{"-\\36 0", "-60"},
{"-\\37 0", "-70"},
{"-\\38 0", "-80"},
{"-\\39 0", "-90"},
// 2-, 3-, and 4-byte UTF-8 (i.e. >= U+0080) is not escaped.
u8"utf8_2byte-\u00a3",
u8"utf8_3byte-\u1d01",
u8"utf8_4byte-\U0002070e",
// Embedded NUL character is changed to the replacement character.
{u8"XX\uFFFDYY", "XX\xc0\x80YY"}
};
// clang-format on
for (const TestPair& identifier : identifiers) {
std::string serialized_identifier;
Serializer serializer(&serialized_identifier);
serializer.SerializeIdentifier(base::Token(identifier.source));
EXPECT_EQ(identifier.serialized, serialized_identifier)
<< " Source: \"" << identifier.source << '"';
}
}
} // namespace cssom
} // namespace cobalt