| <!DOCTYPE html> | |
| <title>CSS Nesting: CSSNestedDeclarations CSSOM</title> | |
| <link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nested-declarations-rule"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script> | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| .a { | |
| & { --x:1; } | |
| --x:2; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| assert_equals(outer.cssRules.length, 2); | |
| assert_equals(outer.cssRules[0].cssText, `& { --x: 1; }`); | |
| assert_equals(outer.cssRules[1].cssText, `--x: 2;`); | |
| }, 'Trailing declarations'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| .a { | |
| --a:1; | |
| --b:1; | |
| & { --c:1; } | |
| --d:1; | |
| --e:1; | |
| & { --f:1; } | |
| --g:1; | |
| --h:1; | |
| --i:1; | |
| & { --j:1; } | |
| --k:1; | |
| --l:1; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| assert_equals(outer.cssRules.length, 6); | |
| assert_equals(outer.cssRules[0].cssText, `& { --c: 1; }`); | |
| assert_equals(outer.cssRules[1].cssText, `--d: 1; --e: 1;`); | |
| assert_equals(outer.cssRules[2].cssText, `& { --f: 1; }`); | |
| assert_equals(outer.cssRules[3].cssText, `--g: 1; --h: 1; --i: 1;`); | |
| assert_equals(outer.cssRules[4].cssText, `& { --j: 1; }`); | |
| assert_equals(outer.cssRules[5].cssText, `--k: 1; --l: 1;`); | |
| }, 'Mixed declarations'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| .a { | |
| & { --x:1; } | |
| --y:2; | |
| --z:3; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| assert_equals(outer.cssRules.length, 2); | |
| let nested_declarations = outer.cssRules[1]; | |
| assert_true(nested_declarations instanceof CSSNestedDeclarations); | |
| assert_equals(nested_declarations.style.length, 2); | |
| assert_equals(nested_declarations.style.getPropertyValue('--x'), ''); | |
| assert_equals(nested_declarations.style.getPropertyValue('--y'), '2'); | |
| assert_equals(nested_declarations.style.getPropertyValue('--z'), '3'); | |
| }, 'CSSNestedDeclarations.style'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| .a { | |
| @media (width > 100px) { | |
| --x:1; | |
| --y:1; | |
| .b { } | |
| --z:1; | |
| } | |
| --w:1; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| assert_equals(outer.cssRules.length, 2); | |
| // @media | |
| let media = outer.cssRules[0]; | |
| assert_equals(media.cssRules.length, 3); | |
| assert_true(media.cssRules[0] instanceof CSSNestedDeclarations); | |
| assert_equals(media.cssRules[0].cssText, `--x: 1; --y: 1;`); | |
| assert_equals(media.cssRules[1].cssText, `& .b { }`); | |
| assert_true(media.cssRules[2] instanceof CSSNestedDeclarations); | |
| assert_equals(media.cssRules[2].cssText, `--z: 1;`); | |
| assert_true(outer.cssRules[1] instanceof CSSNestedDeclarations); | |
| assert_equals(outer.cssRules[1].cssText, `--w: 1;`); | |
| }, 'Nested group rule'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| .a { | |
| @scope (.foo) { | |
| --x:1; | |
| --y:1; | |
| .b { } | |
| --z:1; | |
| } | |
| --w:1; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| if (window.CSSScopeRule) { | |
| assert_equals(outer.cssRules.length, 2); | |
| // @scope | |
| let scope = outer.cssRules[0]; | |
| assert_true(scope instanceof CSSScopeRule); | |
| assert_equals(scope.cssRules.length, 3); | |
| assert_true(scope.cssRules[0] instanceof CSSNestedDeclarations); | |
| assert_equals(scope.cssRules[0].cssText, `--x: 1; --y: 1;`); | |
| assert_equals(scope.cssRules[1].cssText, `.b { }`); // Implicit :scope here. | |
| assert_true(scope.cssRules[2] instanceof CSSNestedDeclarations); | |
| assert_equals(scope.cssRules[2].cssText, `--z: 1;`); | |
| assert_true(outer.cssRules[1] instanceof CSSNestedDeclarations); | |
| assert_equals(outer.cssRules[1].cssText, `--w: 1;`); | |
| } else { | |
| assert_equals(outer.cssRules.length, 0); | |
| } | |
| }, 'Nested @scope rule'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync(` | |
| a { | |
| & { --x:1; } | |
| width: 100px; | |
| height: 200px; | |
| color:hover {} | |
| --y: 2; | |
| } | |
| `); | |
| assert_equals(s.cssRules.length, 1); | |
| let outer = s.cssRules[0]; | |
| assert_equals(outer.cssRules.length, 4); | |
| assert_equals(outer.cssRules[0].cssText, `& { --x: 1; }`); | |
| assert_equals(outer.cssRules[1].cssText, `width: 100px; height: 200px;`); | |
| assert_equals(outer.cssRules[2].cssText, `& color:hover { }`); | |
| assert_equals(outer.cssRules[3].cssText, `--y: 2;`); | |
| }, 'Inner rule starting with an ident'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync('.a {}'); | |
| assert_equals(s.cssRules.length, 1); | |
| let a_rule = s.cssRules[0]; | |
| assert_equals(a_rule.cssRules.length, 0); | |
| a_rule.insertRule(` | |
| width: 100px; | |
| height: 200px; | |
| `); | |
| assert_equals(a_rule.cssRules.length, 1); | |
| assert_true(a_rule.cssRules[0] instanceof CSSNestedDeclarations); | |
| assert_equals(a_rule.cssRules[0].cssText, `width: 100px; height: 200px;`); | |
| }, 'Inserting a CSSNestedDeclaration rule into style rule'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync('.a { @media (width > 100px) {} }'); | |
| assert_equals(s.cssRules.length, 1); | |
| assert_equals(s.cssRules[0].cssRules.length, 1); | |
| let media_rule = s.cssRules[0].cssRules[0]; | |
| assert_true(media_rule instanceof CSSMediaRule); | |
| assert_equals(media_rule.cssRules.length, 0); | |
| media_rule.insertRule(` | |
| width: 100px; | |
| height: 200px; | |
| `); | |
| assert_equals(media_rule.cssRules.length, 1); | |
| assert_true(media_rule.cssRules[0] instanceof CSSNestedDeclarations); | |
| assert_equals(media_rule.cssRules[0].cssText, `width: 100px; height: 200px;`); | |
| }, 'Inserting a CSSNestedDeclaration rule into nested group rule'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync('@media (width > 100px) {}'); | |
| assert_equals(s.cssRules.length, 1); | |
| let media_rule = s.cssRules[0]; | |
| assert_true(media_rule instanceof CSSMediaRule); | |
| assert_equals(media_rule.cssRules.length, 0); | |
| assert_throws_dom('SyntaxError', () => { | |
| media_rule.insertRule(` | |
| width: 100px; | |
| height: 200px; | |
| `); | |
| }); | |
| }, 'Attempting to insert a CSSNestedDeclaration rule into top-level @media rule'); | |
| test(() => { | |
| let sheet = new CSSStyleSheet(); | |
| assert_throws_dom('SyntaxError', () => { | |
| sheet.insertRule(` | |
| width: 100px; | |
| height: 200px; | |
| `); | |
| }); | |
| }, 'Attempting to insert a CSSNestedDeclaration rule into a stylesheet'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync('.a {}'); | |
| assert_equals(s.cssRules.length, 1); | |
| let a_rule = s.cssRules[0]; | |
| assert_equals(a_rule.cssRules.length, 0); | |
| assert_throws_dom('SyntaxError', () => { | |
| a_rule.insertRule(''); | |
| }); | |
| }, 'Attempting to insert a CSSNestedDeclaration rule, empty block'); | |
| test(() => { | |
| let s = new CSSStyleSheet(); | |
| s.replaceSync('.a {}'); | |
| assert_equals(s.cssRules.length, 1); | |
| let a_rule = s.cssRules[0]; | |
| assert_equals(a_rule.cssRules.length, 0); | |
| assert_throws_dom('SyntaxError', () => { | |
| a_rule.insertRule(` | |
| xwidth: 100px; | |
| xheight: 200px; | |
| `); | |
| }); | |
| }, 'Attempting to insert a CSSNestedDeclaration rule, all invalid declarations'); | |
| </script> |