Commit ddd53edd authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

[:is/:where] Test selector serialization rather than parse status

We currently have several unit tests that check that some selector
list containing :is or :where either fails or passes parsing. That's
all well and good when selector lists are parsed the normal way, but
with <forgiving-selector-list> [1] support (in a future CL), a simple
selector :is(...) can't really fail parsing: it will just end up empty
in the worst case.

Hence, this CL makes preparations such that it's easier to write tests
for these "partial" parse failures.

While at it, tests are also converted to parametric tests. This makes
it easier to run individual tests when something is failing.

There should be no behavior change, nor any change in test coverage.

[1] https://drafts.csswg.org/selectors/#typedef-forgiving-selector-list

Bug: 568705
Change-Id: I81119599d378a5067cd3d08c5dbdc7e3de2f1ae2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450153Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814120}
parent 43e51b98
...@@ -24,6 +24,28 @@ typedef struct { ...@@ -24,6 +24,28 @@ typedef struct {
const int b; const int b;
} ANPlusBTestCase; } ANPlusBTestCase;
struct SelectorTestCase {
// The input string to parse as a selector list.
const char* input;
// The expected serialization of the parsed selector list. If nullptr, then
// the expected serialization is the same as the input value.
//
// For selector list that are expected to fail parsing, use the empty
// string "".
const char* expected = nullptr;
};
class SelectorParseTest : public ::testing::TestWithParam<SelectorTestCase> {};
TEST_P(SelectorParseTest, Parse) {
auto param = GetParam();
SCOPED_TRACE(param.input);
CSSSelectorList list = css_test_helpers::ParseSelectorList(param.input);
const char* expected = param.expected ? param.expected : param.input;
EXPECT_EQ(String(expected), list.SelectorsText());
}
TEST(CSSSelectorParserTest, ValidANPlusB) { TEST(CSSSelectorParserTest, ValidANPlusB) {
ANPlusBTestCase test_cases[] = { ANPlusBTestCase test_cases[] = {
{"odd", 2, 1}, {"odd", 2, 1},
...@@ -387,117 +409,104 @@ TEST(CSSSelectorParserTest, InternalPseudo) { ...@@ -387,117 +409,104 @@ TEST(CSSSelectorParserTest, InternalPseudo) {
} }
} }
TEST(CSSSelectorParserTest, InvalidPseudoIsArguments) { // Pseudo-elements are not valid within :is() as per the spec:
// Pseudo-elements are not valid within :is() as per the spec: // https://drafts.csswg.org/selectors-4/#matches
// https://drafts.csswg.org/selectors-4/#matches static const SelectorTestCase invalid_pseudo_is_argments_data[] = {
const char* test_cases[] = {":is(::-webkit-progress-bar)", // clang-format off
":is(::-webkit-progress-value)", {":is(::-webkit-progress-bar)", ""},
":is(::-webkit-slider-runnable-track)", {":is(::-webkit-progress-value)", ""},
":is(::-webkit-slider-thumb)", {":is(::-webkit-slider-runnable-track)", ""},
":is(::after)", {":is(::-webkit-slider-thumb)", ""},
":is(::backdrop)", {":is(::after)", ""},
":is(::before)", {":is(::backdrop)", ""},
":is(::cue)", {":is(::before)", ""},
":is(::first-letter)", {":is(::cue)", ""},
":is(::first-line)", {":is(::first-letter)", ""},
":is(::grammar-error)", {":is(::first-line)", ""},
":is(::marker)", {":is(::grammar-error)", ""},
":is(::placeholder)", {":is(::marker)", ""},
":is(::selection)", {":is(::placeholder)", ""},
":is(::slotted)", {":is(::selection)", ""},
":is(::spelling-error)", {":is(::slotted)", ""},
":is(:after)", {":is(::spelling-error)", ""},
":is(:before)", {":is(:after)", ""},
":is(:cue)", {":is(:before)", ""},
":is(:first-letter)", {":is(:cue)", ""},
":is(:first-line)"}; {":is(:first-letter)", ""},
{":is(:first-line)", ""},
for (auto* test_case : test_cases) { // clang-format on
SCOPED_TRACE(test_case); };
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
}
TEST(CSSSelectorParserTest, ShadowDomV0WithIsAndWhere) {
// To reduce complexity, ShadowDOM v0 features are not supported in
// combination with :is/:where.
const char* test_cases[] = {
// clang-format off
":is(.a) ::content",
":is(.a /deep/ .b)",
":is(::content)",
":is(::shadow)",
":is(::content .a)",
":is(::shadow .b)",
":is(.a)::shadow",
":is(.a) ::content",
":is(.a) ::shadow",
"::content :is(.a)",
"::shadow :is(.a)",
":is(.a) /deep/ .b",
":.a /deep/ :is(.b)",
":where(.a /deep/ .b)",
":where(.a) ::shadow",
// clang-format on
};
for (auto* test_case : test_cases) {
SCOPED_TRACE(test_case);
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid());
}
}
TEST(CSSSelectorParserTest, NestedSelectorValidity) {
const char* invalid_nesting[] = {
// clang-format off
// These pseudos only accept compound selectors:
"::slotted(:is(.a .b))",
"::slotted(:is(.a + .b))",
"::slotted(:is(.a, .b + .c))",
":host(:is(.a .b))",
":host(:is(.a + .b))",
":host(:is(.a, .b + .c))",
":host-context(:is(.a .b))",
":host-context(:is(.a + .b))",
":host-context(:is(.a, .b + .c))",
"::cue(:is(.a .b))",
"::cue(:is(.a + .b))",
"::cue(:is(.a, .b + .c))",
// clang-format on
};
const char* valid_nesting[] = { INSTANTIATE_TEST_SUITE_P(InvalidPseudoIsArguments,
// clang-format off SelectorParseTest,
":is(.a, .b)", testing::ValuesIn(invalid_pseudo_is_argments_data));
":is(.a .b, .c)",
":is(.a :is(.b .c), .d)", // To reduce complexity, ShadowDOM v0 features are not supported in
":is(.a :where(.b .c), .d)", // combination with :is/:where.
":where(.a :is(.b .c), .d)", static const SelectorTestCase shadow_v0_with_is_where_data[] = {
"::slotted(:is(.a))", // clang-format off
"::slotted(:is(div.a))", {":is(.a) ::content", ""},
"::slotted(:is(.a, .b))", {":is(.a /deep/ .b)", ""},
":host(:is(.a))", {":is(::content)", ""},
":host(:is(div.a))", {":is(::shadow)", ""},
":host(:is(.a, .b))", {":is(::content .a)", ""},
":host-context(:is(.a))", {":is(::shadow .b)", ""},
":host-context(:is(div.a))", {":is(.a)::shadow", ""},
":host-context(:is(.a, .b))", {":is(.a) ::content", ""},
"::cue(:is(.a))", {":is(.a) ::shadow", ""},
"::cue(:is(div.a))", {"::content :is(.a)", ""},
"::cue(:is(.a, .b))", {"::shadow :is(.a)", ""},
// clang-format on {":is(.a) /deep/ .b", ""},
}; {":.a /deep/ :is(.b)", ""},
{":where(.a /deep/ .b)", ""},
{":where(.a) ::shadow", ""},
// clang-format on
};
for (auto* test_case : invalid_nesting) { INSTANTIATE_TEST_SUITE_P(ShadowDomV0WithIsAndWhere,
SCOPED_TRACE(test_case); SelectorParseTest,
EXPECT_FALSE(css_test_helpers::ParseSelectorList(test_case).IsValid()); testing::ValuesIn(shadow_v0_with_is_where_data));
}
static const SelectorTestCase is_where_nesting_data[] = {
// clang-format off
// These pseudos only accept compound selectors:
{"::slotted(:is(.a .b))", ""},
{"::slotted(:is(.a + .b))", ""},
{"::slotted(:is(.a, .b + .c))", ""},
{":host(:is(.a .b))", ""},
{":host(:is(.a + .b))", ""},
{":host(:is(.a, .b + .c))", ""},
{":host-context(:is(.a .b))", ""},
{":host-context(:is(.a + .b))", ""},
{":host-context(:is(.a, .b + .c))", ""},
{"::cue(:is(.a .b))", ""},
{"::cue(:is(.a + .b))", ""},
{"::cue(:is(.a, .b + .c))", ""},
// Valid selectors:
{":is(.a, .b)"},
{":is(.a .b, .c)"},
{":is(.a :is(.b .c), .d)"},
{":is(.a :where(.b .c), .d)"},
{":where(.a :is(.b .c), .d)"},
{"::slotted(:is(.a))"},
{"::slotted(:is(div.a))"},
{"::slotted(:is(.a, .b))"},
{":host(:is(.a))"},
{":host(:is(div.a))"},
{":host(:is(.a, .b))"},
{":host-context(:is(.a))"},
{":host-context(:is(div.a))"},
{":host-context(:is(.a, .b))"},
{"::cue(:is(.a))"},
{"::cue(:is(div.a))"},
{"::cue(:is(.a, .b))"},
// clang-format on
};
for (auto* test_case : valid_nesting) { INSTANTIATE_TEST_SUITE_P(NestedSelectorValidity,
SCOPED_TRACE(test_case); SelectorParseTest,
EXPECT_TRUE(css_test_helpers::ParseSelectorList(test_case).IsValid()); testing::ValuesIn(is_where_nesting_data));
}
}
namespace { namespace {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment