| <!doctype html> |
| <title>CSS Selectors parsing</title> |
| <link rel="author" title="Adam Argyle" href="mailto:argyle@google.com"> |
| <link rel="author" title="Tab Atkins-Bittner" href="https://tabatkins.com/contact/"> |
| <link rel="help" href="https://drafts.csswg.org/css-nesting-1/"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| |
| <style id="test-sheet"></style> |
| <script> |
| let [ss] = document.styleSheets |
| |
| function resetStylesheet() { |
| while (ss.rules.length) |
| ss.removeRule(0) |
| } |
| |
| function testNestedSelector(sel, {expected=sel, parent=".foo"}={}) { |
| resetStylesheet(); |
| const ruleText = `${parent} { ${sel} { color: green; }}` |
| test(()=>{ |
| ss.insertRule(ruleText); |
| assert_equals(ss.rules.length, 1, "Outer rule should exist."); |
| const rule = ss.rules[0]; |
| assert_equals(rule.cssRules.length, 1, "Inner rule should exist."); |
| const innerRule = rule.cssRules[0]; |
| assert_equals(innerRule.selectorText, expected, `Inner rule's selector should be "${expected}".`); |
| }, ruleText); |
| } |
| |
| function testInvalidNestingSelector(sel, {parent=".foo"}={}) { |
| resetStylesheet(); |
| const ruleText = `${parent} { ${sel} { color: green; }}` |
| test(()=>{ |
| ss.insertRule(ruleText); |
| assert_equals(ss.rules.length, 1, "Outer rule should exist."); |
| const rule = ss.rules[0]; |
| assert_equals(rule.cssRules.length, 0, "Inner rule should not exist."); |
| }, "INVALID: " + ruleText); |
| } |
| |
| // basic usage |
| testNestedSelector("&"); |
| testNestedSelector("&.bar"); |
| testNestedSelector("& .bar"); |
| testNestedSelector("& > .bar"); |
| |
| // relative selector |
| testNestedSelector("> .bar", {expected:"& > .bar"}); |
| testNestedSelector("> & .bar", {expected:"& > & .bar"}); |
| testNestedSelector("+ .bar &", {expected:"& + .bar &"}); |
| testNestedSelector("+ .bar, .foo, > .baz", {expected:"& + .bar, & .foo, & > .baz"}); |
| |
| // implicit relative (and not) |
| testNestedSelector(".foo", {expected:"& .foo"}); |
| testNestedSelector(".test > & .bar"); |
| testNestedSelector(".foo, .foo &", {expected:"& .foo, .foo &"}); |
| testNestedSelector(":is(.bar, .baz)", {expected:"& :is(.bar, .baz)"}); |
| testNestedSelector("&:is(.bar, .baz)"); |
| testNestedSelector(":is(.bar, &.baz)"); |
| testNestedSelector("&:is(.bar, &.baz)"); |
| |
| // Mixing nesting selector with other simple selectors |
| testNestedSelector("div&"); |
| testInvalidNestingSelector("&div"); // type selector must be first |
| testNestedSelector(".class&"); |
| testNestedSelector("&.class"); |
| testNestedSelector("[attr]&"); |
| testNestedSelector("&[attr]"); |
| testNestedSelector("#id&"); |
| testNestedSelector("&#id"); |
| testNestedSelector(":hover&"); |
| testNestedSelector("&:hover"); |
| testNestedSelector(":is(div)&"); |
| testNestedSelector("&:is(div)"); |
| |
| // Multiple nesting selectors |
| testNestedSelector("& .bar & .baz & .qux"); |
| testNestedSelector("&&"); |
| |
| // Selector list in inner rule |
| testNestedSelector("& > section, & > article"); |
| |
| // Selector list in both inner and outer rule. |
| testNestedSelector("& + .baz, &.qux", {parent:".foo, .bar"}); |
| </script> |