summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-nesting/parsing.html
blob: a97716ff9d5a4d59b7090992bc9dc11268c36aff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<!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(".foo, .bar", {expected:"& .foo, & .bar"});
  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>