| // 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 |