LibWeb: Import a bunch of :has() selector tests from WPT

This commit is contained in:
Andreas Kling
2024-10-27 12:33:57 +01:00
committed by Andreas Kling
parent 9e080e197c
commit a640fcc693
Notes: github-actions[bot] 2024-10-27 12:34:43 +00:00
44 changed files with 4493 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has pseudo class behavior with explicit ':scope' in its argument</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<main>
<div id=d01 class="a">
<div id=scope1 class="b">
<div id=d02 class="c">
<div id=d03 class="c">
<div id=d04 class="d"></div>
</div>
</div>
<div id=d05 class="e"></div>
</div>
</div>
<div id=d06>
<div id=scope2 class="b">
<div id=d07 class="c">
<div id=d08 class="c">
<div id=d09></div>
</div>
</div>
</div>
</div>
</div>
<script>
function formatElements(elements) {
return elements.map(e => e.id).sort().join();
}
// Test that |selector| returns the given elements in the given scope element
function test_selector_all(scope, selector, expected) {
test(function() {
let actual = Array.from(scope.querySelectorAll(selector));
assert_equals(formatElements(actual), formatElements(expected));
}, `${selector} matches expected elements on ${scope.id}`);
}
// Test that |selector1| and |selector2| returns same elements in the given scope element
function compare_selector_all(scope, selector1, selector2) {
test(function() {
let result1 = Array.from(scope.querySelectorAll(selector1));
let result2 = Array.from(scope.querySelectorAll(selector2));
assert_equals(formatElements(result1), formatElements(result2));
}, `${selector1} and ${selector2} returns same elements on ${scope.id}`);
}
// descendants of a scope element cannot have the scope element as its descendant
test_selector_all(scope1, ':has(:scope)', []);
test_selector_all(scope1, ':has(:scope .c)', []);
test_selector_all(scope1, ':has(.a :scope)', []);
// there can be more simple and efficient alternative for a ':scope' in ':has'
test_selector_all(scope1, '.a:has(:scope) .c', [d02, d03]);
compare_selector_all(scope1, '.a:has(:scope) .c', ':is(.a :scope .c)');
test_selector_all(scope2, '.a:has(:scope) .c', []);
compare_selector_all(scope2, '.a:has(:scope) .c', ':is(.a :scope .c)');
test_selector_all(scope1, '.c:has(:is(:scope .d))', [d02, d03]);
compare_selector_all(scope1, '.c:has(:is(:scope .d))', ':scope .c:has(.d)');
compare_selector_all(scope1, '.c:has(:is(:scope .d))', '.c:has(.d)');
test_selector_all(scope2, '.c:has(:is(:scope .d))', []);
compare_selector_all(scope2, '.c:has(:is(:scope .d))', ':scope .c:has(.d)');
compare_selector_all(scope2, '.c:has(:is(:scope .d))', '.c:has(.d)');
</script>

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Basic matching behavior of :has pseudo class</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<main id=main>
<div id=a class="ancestor">
<div id=b class="parent ancestor">
<div id=c class="sibling descendant">
<div id=d class="descendant"></div>
</div>
<div id=e class="target descendant"></div>
</div>
<div id=f class="parent ancestor">
<div id=g class="target descendant"></div>
</div>
<div id=h class="parent ancestor">
<div id=i class="target descendant"></div>
<div id=j class="sibling descendant">
<div id=k class="descendant"></div>
</div>
</div>
</div>
</main>
<script>
function formatElements(elements) {
return elements.map(e => e.id).sort().join();
}
// Test that |selector| returns the given elements in #main.
function test_selector_all(selector, expected) {
test(function() {
let actual = Array.from(main.querySelectorAll(selector));
assert_equals(formatElements(actual), formatElements(expected));
}, `${selector} matches expected elements`);
}
// Test that |selector| returns the given element in #main.
function test_selector(selector, expected) {
test(function() {
assert_equals(main.querySelector(selector), expected);
}, `${selector} matches expected element`);
}
// Test that |selector| returns the given closest element.
function test_closest(node, selector, expected) {
test(function() {
assert_equals(node.closest(selector), expected);
}, `closest(${selector}) returns expected element`);
}
// Test that |selector| returns matching status.
function test_matches(node, selector, expected) {
test(function() {
assert_equals(node.matches(selector), expected);
}, `${selector} matches expectedly`);
}
test_selector_all(':has(#a)', []);
test_selector_all(':has(.ancestor)', [a]);
test_selector_all(':has(.target)', [a, b, f, h]);
test_selector_all(':has(.descendant)', [a, b, c, f, h, j]);
test_selector_all('.parent:has(.target)', [b, f, h]);
test_selector_all(':has(.sibling ~ .target)', [a, b]);
test_selector_all('.parent:has(.sibling ~ .target)', [b]);
test_selector_all(':has(:is(.target ~ .sibling .descendant))', [a, h, j]);
test_selector_all('.parent:has(:is(.target ~ .sibling .descendant))', [h]);
test_selector_all('.sibling:has(.descendant) ~ .target', [e]);
test_selector_all(':has(> .parent)', [a]);
test_selector_all(':has(> .target)', [b, f, h]);
test_selector_all(':has(> .parent, > .target)', [a, b, f, h]);
test_selector_all(':has(+ #h)', [f]);
test_selector_all('.parent:has(~ #h)', [b, f]);
test_selector('.sibling:has(.descendant)', c);
test_closest(k, '.ancestor:has(.descendant)', h);
test_matches(h, ':has(.target ~ .sibling .descendant)', true);
</script>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Matches :has pseudo class to uninserted elements</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<script>
// Test that |selector| returns matching status.
function test_matches(node, selector, expected) {
test(function() {
assert_equals(node.matches(selector), expected);
}, `${selector} matches expectedly`);
}
subject = document.createElement('subject');
subject.innerHTML = '<child></child>';
test_matches(subject, ':has(child)', true);
test_matches(subject, ':has(> child)', true);
subject.innerHTML = '<child><descendant></descendant></child>';
test_matches(subject, ':has(descendant)', true);
test_matches(subject, ':has(> descendant)', false);
subject.innerHTML = '<child></child><direct_sibling></direct_sibling><indirect_sibling></indirect_sibling>';
test_matches(subject.firstChild, ':has(~ direct_sibling)', true);
test_matches(subject.firstChild, ':has(+ direct_sibling)', true);
test_matches(subject.firstChild, ':has(~ indirect_sibling)', true);
test_matches(subject.firstChild, ':has(+ indirect_sibling)', false);
test_matches(subject, ':has(*)', true);
test_matches(subject, ':has(> *)', true);
test_matches(subject, ':has(~ *)', false);
test_matches(subject, ':has(+ *)', false);
</script>

View File

@@ -0,0 +1,184 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has pseudo class behavior with various relative arguments</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<main id=main>
<div id=d01>
<div id=d02 class="x">
<div id=d03 class="a"></div>
<div id=d04></div>
<div id=d05 class="b"></div>
</div>
<div id=d06 class="x">
<div id=d07 class="x">
<div id=d08 class="a"></div>
</div>
</div>
<div id=d09 class="x">
<div id=d10 class="a">
<div id=d11 class="b"></div>
</div>
</div>
<div id=d12 class="x">
<div id=d13 class="a">
<div id=d14>
<div id=d15 class="b"></div>
</div>
</div>
<div id=d16 class="b"></div>
</div>
</div>
<div id=d17>
<div id=d18 class="x"></div>
<div id=d19 class="x"></div>
<div id=d20 class="a"></div>
<div id=d21 class="x"></div>
<div id=d22 class="a">
<div id=d23 class="b"></div>
</div>
<div id=d24 class="x"></div>
<div id=d25 class="a">
<div id=d26>
<div id=d27 class="b"></div>
</div>
</div>
<div id=d28 class="x"></div>
<div id=d29 class="a"></div>
<div id=d30 class="b">
<div id=d31 class="c"></div>
</div>
<div id=d32 class="x"></div>
<div id=d33 class="a"></div>
<div id=d34 class="b">
<div id=d35>
<div id=d36 class="c"></div>
</div>
</div>
<div id=d37 class="x"></div>
<div id=d38 class="a"></div>
<div id=d39 class="b"></div>
<div id=d40 class="x"></div>
<div id=d41 class="a"></div>
<div id=d42></div>
<div id=d43 class="b">
<div id=d44 class="x">
<div id=d45 class="c"></div>
</div>
</div>
<div id=d46 class="x"></div>
<div id=d47 class="a">
</div>
</div>
<div>
<div id=d48 class="x">
<div id=d49 class="x">
<div id=d50 class="x d">
<div id=d51 class="x d">
<div id=d52 class="x">
<div id=d53 class="x e">
<div id=d54 class="f"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id=d55 class="x"></div>
<div id=d56 class="x d"></div>
<div id=d57 class="x d"></div>
<div id=d58 class="x"></div>
<div id=d59 class="x e"></div>
<div id=d60 class="f"></div>
</div>
<div>
<div id=d61 class="x"></div>
<div id=d62 class="x y"></div>
<div id=d63 class="x y">
<div id=d64 class="y g">
<div id=d65 class="y">
<div id=d66 class="y h">
<div id=d67 class="i"></div>
</div>
</div>
</div>
</div>
<div id=d68 class="x y">
<div id=d69 class="x"></div>
<div id=d70 class="x"></div>
<div id=d71 class="x y">
<div id=d72 class="y g">
<div id=d73 class="y">
<div id=d74 class="y h">
<div id=d75 class="i"></div>
</div>
</div>
</div>
</div>
<div id=d76 class="x"></div>
<div id=d77 class="j"><div id=d78><div id=d79></div></div></div>
</div>
<div id=d80 class="j"></div>
</div>
</main>
<script>
function formatElements(elements) {
return elements.map(e => e.id).sort().join();
}
// Test that |selector| returns the given elements in #main.
function test_selector_all(selector, expected) {
test(function() {
let actual = Array.from(main.querySelectorAll(selector));
assert_equals(formatElements(actual), formatElements(expected));
}, `${selector} matches expected elements`);
}
test_selector_all('.x:has(.a)', [d02, d06, d07, d09, d12]);
test_selector_all('.x:has(.a > .b)', [d09]);
test_selector_all('.x:has(.a .b)', [d09, d12]);
test_selector_all('.x:has(.a + .b)', [d12]);
test_selector_all('.x:has(.a ~ .b)', [d02, d12]);
test_selector_all('.x:has(> .a)', [d02, d07, d09, d12]);
test_selector_all('.x:has(> .a > .b)', [d09]);
test_selector_all('.x:has(> .a .b)', [d09, d12]);
test_selector_all('.x:has(> .a + .b)', [d12]);
test_selector_all('.x:has(> .a ~ .b)', [d02, d12]);
test_selector_all('.x:has(+ .a)', [d19, d21, d24, d28, d32, d37, d40, d46]);
test_selector_all('.x:has(+ .a > .b)', [d21]);
test_selector_all('.x:has(+ .a .b)', [d21, d24]);
test_selector_all('.x:has(+ .a + .b)', [d28, d32, d37]);
test_selector_all('.x:has(+ .a ~ .b)', [d19, d21, d24, d28, d32, d37, d40]);
test_selector_all('.x:has(~ .a)', [d18, d19, d21, d24, d28, d32, d37, d40, d46]);
test_selector_all('.x:has(~ .a > .b)', [d18, d19, d21]);
test_selector_all('.x:has(~ .a .b)', [d18, d19, d21, d24]);
test_selector_all('.x:has(~ .a + .b)', [d18, d19, d21, d24, d28, d32, d37]);
test_selector_all('.x:has(~ .a + .b > .c)', [d18, d19, d21, d24, d28]);
test_selector_all('.x:has(~ .a + .b .c)', [d18, d19, d21, d24, d28, d32]);
test_selector_all('.x:has(.d .e)', [d48, d49, d50]);
test_selector_all('.x:has(.d .e) .f', [d54]);
test_selector_all('.x:has(> .d)', [d49, d50]);
test_selector_all('.x:has(> .d) .f', [d54]);
test_selector_all('.x:has(~ .d ~ .e)', [d48, d55, d56]);
test_selector_all('.x:has(~ .d ~ .e) ~ .f', [d60]);
test_selector_all('.x:has(+ .d ~ .e)', [d55, d56]);
test_selector_all('.x:has(+ .d ~ .e) ~ .f', [d60]);
test_selector_all('.y:has(> .g .h)', [d63, d71])
test_selector_all('.y:has(.g .h)', [d63, d68, d71])
test_selector_all('.y:has(> .g .h) .i', [d67, d75])
test_selector_all('.y:has(.g .h) .i', [d67, d75])
test_selector_all('.d .x:has(.e)', [d51, d52])
test_selector_all('.d ~ .x:has(~ .e)', [d57, d58])
</script>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<title>CSS Selectors Test: Chrome crash issue 1470477</title>
<link rel="help" href="https://crbug.com/1470477">
<style>
:has(> :where(label:first-child + [a="a"]:only-of-type,
[a="a"]:only-of-type + label:last-child)) label:last-child {
margin-inline: 1em;
}
</style>
<p>PASS if this tests does not crash</p>
<script>
if (window.internals)
window.internals.signalTextTestIsDone("PASS");
</script>

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<title>Specificity for complex :has selectors</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#specificity-rules">
<style>
main :has(#foo) { --t0:PASS; }
main :has(.foo) { --t0:FAIL; }
main :has(span#foo) { --t1:PASS; }
main :has(#foo) { --t1:FAIL; }
main :has(.bar, #foo) { --t2:FAIL; }
main :has(#foo, .bar) { --t2:PASS; }
main :has(.bar, #foo) { --t3:PASS; }
main :has(.foo, .bar) { --t3:FAIL; }
main :has(span + span) { --t4:PASS; }
main :has(span) { --t4:FAIL; }
main :has(span, li, #foo) { --t5:PASS; }
main :has(span, li, p) { --t5:FAIL; }
main div:has(.foo) { --t6:FAIL; }
main div.baz { --t6:PASS; }
main div.baz { --t7:FAIL; }
main div:has(.foo) { --t7:PASS; }
</style>
<main id=main>
<div id=div class=baz><p><span id=foo class=foo></span><span class=bar></span><li></li></p></div>
</main>
<script>
function test_value(name, description) {
test(function() {
let actual = getComputedStyle(div).getPropertyValue(name);
assert_equals(actual, 'PASS');
}, description);
}
test_value('--t0', ':has(#foo) wins over :has(.foo)');
test_value('--t1', ':has(span#foo) wins over :has(#foo)');
test_value('--t2', ':has(.bar, #foo) has same specificity as :has(#foo, .bar)');
test_value('--t3', ':has(.bar, #foo) wins over :has(.foo, .bar)');
test_value('--t4', ':has(span + span) wins over :has(span)');
test_value('--t5', ':has(span, li, p) wins over :has(span, lo, p)');
test_value('--t6', 'latter .baz wins over :has(.foo)');
test_value('--t7', 'latter :has(.foo) wins over .baz');
</script>

View File

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has() invalidation with nesting where the selector is shared</title>
<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#outer1:has(.test) {
& #subject1_1 {
color: red;
}
& + #subject1_2 {
color: orangered;
}
}
#outer2:has(.test) {
& .ancestor {
& #subject2_1 {
color: green;
}
& + #subject2_2 {
color: lightgreen;
}
}
}
#outer3:is(:has(.test) .outer) {
& #subject3_1 {
color: blue;
}
& + #subject3_2 {
color: skyblue;
}
}
</style>
<main id="main">
<div>
<div id="outer1">
<div id="trigger1"></div>
<div id="subject1_1"></div>
</div>
<div id="subject1_2"></div>
</div>
<div id="outer2">
<div id="trigger2"></div>
<div class="ancestor">
<div id="subject2_1"></div>
</div>
<div id="subject2_2"></div>
</div>
<div id="trigger3">
<div id="outer3" class="outer">
<div id="subject3_1"></div>
</div>
<div id="subject3_2"></div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const orangered = 'rgb(255, 69, 0)';
const green = 'rgb(0, 128, 0)';
const lightgreen = 'rgb(144, 238, 144)';
const blue = 'rgb(0, 0, 255)';
const skyblue = 'rgb(135, 206, 235)';
const colors = {
red: {
descendant: red,
sibling: orangered,
},
green: {
descendant: green,
sibling: lightgreen,
},
blue: {
descendant: blue,
sibling: skyblue,
},
};
function testColor(testName, element, color) {
test(function() {
assert_equals(getComputedStyle(element).color, color);
}, testName);
}
function testClassChange(trigger, targetDescendant, targetSibling, expected)
{
trigger.classList.add('test');
testColor(`add .test to ${trigger.id} - check ${targetDescendant.id}`, targetDescendant, colors[expected].descendant);
testColor(`add .test to ${trigger.id} - check ${targetSibling.id}`, targetSibling, colors[expected].sibling);
trigger.classList.remove('test');
testColor(`remove .test from ${trigger.id} - check ${targetDescendant.id}`, targetDescendant, grey);
testColor(`remove .test from ${trigger.id} - check ${targetSibling.id}`, targetSibling, grey);
}
testClassChange(trigger1, subject1_1, subject1_2, 'red');
testClassChange(trigger2, subject2_1, subject2_2, 'green');
testClassChange(trigger3, subject3_1, subject3_2, 'blue');
</script>

View File

@@ -0,0 +1,312 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() in adjacent position</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
div:has(.test) + #subject { color: red }
div:has([test_attr]) + #subject { color: orangered }
div:has(> .test) + #subject { color: green }
div:has(> [test_attr]) + #subject { color: lightgreen }
div:has(~ .test) + #subject { color: yellow }
div:has(~ [test_attr]) + #subject { color: ivory }
div:has(+ .test) + #subject { color: blue }
div:has(+ [test_attr]) + #subject { color: skyblue }
div:has(~ div .test) + #subject { color: purple }
div:has(~ div [test_attr]) + #subject { color: violet }
div:has(+ div .test) + #subject { color: pink }
div:has(+ div [test_attr]) + #subject { color: lightpink }
</style>
<main id=main>
<div id=previous_sibling>
<div id=previous_sibling_child>
<div id=previous_sibling_descendant></div>
</div>
</div>
<div id=subject></div>
<div id=next_sibling>
<div id=next_sibling_child>
<div id=next_sibling_descendant></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const orangered = 'rgb(255, 69, 0)';
const green = 'rgb(0, 128, 0)';
const lightgreen = 'rgb(144, 238, 144)';
const blue = 'rgb(0, 0, 255)';
const skyblue = 'rgb(135, 206, 235)';
const yellow = 'rgb(255, 255, 0)';
const ivory = 'rgb(255, 255, 240)';
const purple = 'rgb(128, 0, 128)';
const violet = 'rgb(238, 130, 238)';
const pink = 'rgb(255, 192, 203)';
const lightpink = 'rgb(255, 182, 193)';
const colors = {
grey: {
classTest: grey,
attributeTest: grey,
},
red: {
classTest: red,
attributeTest: orangered,
},
green: {
classTest: green,
attributeTest: lightgreen,
},
blue: {
classTest: blue,
attributeTest: skyblue,
},
yellow: {
classTest: yellow,
attributeTest: ivory,
},
purple: {
classTest: purple,
attributeTest: violet,
},
pink: {
classTest: pink,
attributeTest: lightpink,
},
};
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
element.classList.add('test');
testColor(`add .test to ${element.id}`, expectedColorForClassTest);
element.classList.remove('test');
testColor(`remove .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
beforeElement.before(newElement);
testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test before ${beforeElement.id}`, grey);
newElement.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert element div before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove element div before ${beforeElement.id}`, grey);
newElement.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
afterElement.after(newElement);
testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test after ${afterElement.id}`, grey);
newElement.classList.remove('test');
afterElement.after(newElement);
testColor(`insert element div after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove element div after ${afterElement.id}`, grey);
newElement.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
newChild.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert tree div>div before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div before ${beforeElement.id}`, grey);
newChild.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
newChild.classList.remove('test');
afterElement.after(newElement);
testColor(`insert tree div>div after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div after ${afterElement.id}`, grey);
newChild.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(previous_sibling, "grey");
testClassChange(previous_sibling_child, "green");
testClassChange(previous_sibling_descendant, "red");
testClassChange(subject, "blue");
testClassChange(next_sibling, "yellow");
testClassChange(next_sibling_child, "purple");
testClassChange(next_sibling_descendant, "purple");
testElementInsertionBefore(previous_sibling, "grey");
testElementInsertionBefore(previous_sibling_child, "green");
testElementInsertionBefore(previous_sibling_descendant, "red");
testElementInsertionBefore(subject, "grey");
testElementInsertionBefore(next_sibling, "yellow");
testElementInsertionBefore(next_sibling_child, "purple");
testElementInsertionBefore(next_sibling_descendant, "purple");
testElementInsertionAfter(previous_sibling, "grey");
testElementInsertionAfter(previous_sibling_child, "green");
testElementInsertionAfter(previous_sibling_descendant, "red");
testElementInsertionAfter(subject, "yellow");
testElementInsertionAfter(next_sibling, "yellow");
testElementInsertionAfter(next_sibling_child, "purple");
testElementInsertionAfter(next_sibling_descendant, "purple");
testTreeInsertionBefore(previous_sibling, "grey");
testTreeInsertionBefore(previous_sibling_child, "red");
testTreeInsertionBefore(previous_sibling_descendant, "red");
testTreeInsertionBefore(subject, "green");
testTreeInsertionBefore(next_sibling, "purple");
testTreeInsertionBefore(next_sibling_child, "purple");
testTreeInsertionBefore(next_sibling_descendant, "purple");
testTreeInsertionAfter(previous_sibling, "green");
testTreeInsertionAfter(previous_sibling_child, "red");
testTreeInsertionAfter(previous_sibling_descendant, "red");
testTreeInsertionAfter(subject, "purple");
testTreeInsertionAfter(next_sibling, "purple");
testTreeInsertionAfter(next_sibling_child, "purple");
testTreeInsertionAfter(next_sibling_descendant, "purple");
</script>

View File

@@ -0,0 +1,320 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() in ancestor position</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
div:has(.test) #subject { color: red }
div:has([test_attr]) #subject { color: orangered }
div:has(> .test) #subject { color: green }
div:has(> [test_attr]) #subject { color: lightgreen }
div:has(~ .test) #subject { color: yellow }
div:has(~ [test_attr]) #subject { color: ivory }
div:has(+ .test) #subject { color: blue }
div:has(+ [test_attr]) #subject { color: skyblue }
div:has(~ div .test) #subject { color: purple }
div:has(~ div [test_attr]) #subject { color: violet }
div:has(+ div .test) #subject { color: pink }
div:has(+ div [test_attr]) #subject { color: lightpink }
</style>
<main id=main>
<div id=subject_ancestor>
<div id=subject_parent>
<div id=subject>
<div id=subject_child>
<div id=subject_descendant></div>
</div>
</div>
</div>
</div>
<div id=next_sibling>
<div id=next_sibling_child>
<div id=next_sibling_descendant></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const orangered = 'rgb(255, 69, 0)';
const green = 'rgb(0, 128, 0)';
const lightgreen = 'rgb(144, 238, 144)';
const blue = 'rgb(0, 0, 255)';
const skyblue = 'rgb(135, 206, 235)';
const yellow = 'rgb(255, 255, 0)';
const ivory = 'rgb(255, 255, 240)';
const purple = 'rgb(128, 0, 128)';
const violet = 'rgb(238, 130, 238)';
const pink = 'rgb(255, 192, 203)';
const lightpink = 'rgb(255, 182, 193)';
const colors = {
grey: {
classTest: grey,
attributeTest: grey,
},
red: {
classTest: red,
attributeTest: orangered,
},
green: {
classTest: green,
attributeTest: lightgreen,
},
blue: {
classTest: blue,
attributeTest: skyblue,
},
yellow: {
classTest: yellow,
attributeTest: ivory,
},
purple: {
classTest: purple,
attributeTest: violet,
},
pink: {
classTest: pink,
attributeTest: lightpink,
},
};
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
element.classList.add('test');
testColor(`add .test to ${element.id}`, expectedColorForClassTest);
element.classList.remove('test');
testColor(`remove .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
beforeElement.before(newElement);
testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test before ${beforeElement.id}`, grey);
newElement.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert element div before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove element div before ${beforeElement.id}`, grey);
newElement.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
afterElement.after(newElement);
testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test after ${afterElement.id}`, grey);
newElement.classList.remove('test');
afterElement.after(newElement);
testColor(`insert element div after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove element div after ${afterElement.id}`, grey);
newElement.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
newChild.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert tree div>div before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div before ${beforeElement.id}`, grey);
newChild.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
newChild.classList.remove('test');
afterElement.after(newElement);
testColor(`insert tree div>div after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div after ${afterElement.id}`, grey);
newChild.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(subject_ancestor, "grey");
testClassChange(subject_parent, "green");
testClassChange(subject, "green");
testClassChange(subject_child, "red");
testClassChange(subject_descendant, "red");
testClassChange(next_sibling, "blue");
testClassChange(next_sibling_child, "pink");
testClassChange(next_sibling_descendant, "pink");
testElementInsertionBefore(subject_ancestor, "grey");
testElementInsertionBefore(subject_parent, "green");
testElementInsertionBefore(subject, "green");
testElementInsertionBefore(subject_child, "red");
testElementInsertionBefore(subject_descendant, "red");
testElementInsertionBefore(next_sibling, "blue");
testElementInsertionBefore(next_sibling_child, "pink");
testElementInsertionBefore(next_sibling_descendant, "pink");
testElementInsertionAfter(subject_ancestor, "blue");
testElementInsertionAfter(subject_parent, "blue");
testElementInsertionAfter(subject, "green");
testElementInsertionAfter(subject_child, "red");
testElementInsertionAfter(subject_descendant, "red");
testElementInsertionAfter(next_sibling, "yellow");
testElementInsertionAfter(next_sibling_child, "pink");
testElementInsertionAfter(next_sibling_descendant, "pink");
testTreeInsertionBefore(subject_ancestor, "grey");
testTreeInsertionBefore(subject_parent, "red");
testTreeInsertionBefore(subject, "red");
testTreeInsertionBefore(subject_child, "red");
testTreeInsertionBefore(subject_descendant, "red");
testTreeInsertionBefore(next_sibling, "pink");
testTreeInsertionBefore(next_sibling_child, "pink");
testTreeInsertionBefore(next_sibling_descendant, "pink");
testTreeInsertionAfter(subject_ancestor, "pink");
testTreeInsertionAfter(subject_parent, "pink");
testTreeInsertionAfter(subject, "red");
testTreeInsertionAfter(subject_child, "red");
testTreeInsertionAfter(subject_descendant, "red");
testTreeInsertionAfter(next_sibling, "purple");
testTreeInsertionAfter(next_sibling_child, "pink");
testTreeInsertionAfter(next_sibling_descendant, "pink");
</script>

View File

@@ -0,0 +1,300 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() in parent position</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
div:has(.test) > #subject { color: red }
div:has([test_attr]) > #subject { color: orangered }
div:has(> .test) > #subject { color: green }
div:has(> [test_attr]) > #subject { color: lightgreen }
div:has(~ .test) > #subject { color: yellow }
div:has(~ [test_attr]) > #subject { color: ivory }
div:has(+ .test) > #subject { color: blue }
div:has(+ [test_attr]) > #subject { color: skyblue }
div:has(~ div .test) > #subject { color: purple }
div:has(~ div [test_attr]) > #subject { color: violet }
div:has(+ div .test) > #subject { color: pink }
div:has(+ div [test_attr]) > #subject { color: lightpink }
</style>
<main id=main>
<div id=subject_ancestor>
<div id=subject_parent>
<div id=subject>
<div id=subject_child>
<div id=subject_descendant></div>
</div>
</div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const orangered = 'rgb(255, 69, 0)';
const green = 'rgb(0, 128, 0)';
const lightgreen = 'rgb(144, 238, 144)';
const blue = 'rgb(0, 0, 255)';
const skyblue = 'rgb(135, 206, 235)';
const yellow = 'rgb(255, 255, 0)';
const ivory = 'rgb(255, 255, 240)';
const purple = 'rgb(128, 0, 128)';
const violet = 'rgb(238, 130, 238)';
const pink = 'rgb(255, 192, 203)';
const lightpink = 'rgb(255, 182, 193)';
const colors = {
grey: {
classTest: grey,
attributeTest: grey,
},
red: {
classTest: red,
attributeTest: orangered,
},
green: {
classTest: green,
attributeTest: lightgreen,
},
blue: {
classTest: blue,
attributeTest: skyblue,
},
yellow: {
classTest: yellow,
attributeTest: ivory,
},
purple: {
classTest: purple,
attributeTest: violet,
},
pink: {
classTest: pink,
attributeTest: lightpink,
},
};
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
element.classList.add('test');
testColor(`add .test to ${element.id}`, expectedColorForClassTest);
element.classList.remove('test');
testColor(`remove .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test');
beforeElement.before(newElement);
testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test before ${beforeElement.id}`, grey);
newElement.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert element div before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove element div before ${beforeElement.id}`, grey);
newElement.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test');
afterElement.after(newElement);
testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test after ${afterElement.id}`, grey);
newElement.classList.remove('test');
afterElement.after(newElement);
testColor(`insert element div after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove element div after ${afterElement.id}`, grey);
newElement.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
newChild.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert tree div>div before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div before ${beforeElement.id}`, grey);
newChild.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
newChild.classList.remove('test');
afterElement.after(newElement);
testColor(`insert tree div>div after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div after ${afterElement.id}`, grey);
newChild.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(subject_ancestor, "grey");
testClassChange(subject_parent, "grey");
testClassChange(subject, "green");
testClassChange(subject_child, "red");
testClassChange(subject_descendant, "red");
testElementInsertionBefore(subject_ancestor, "grey");
testElementInsertionBefore(subject_parent, "grey");
testElementInsertionBefore(subject, "green");
testElementInsertionBefore(subject_child, "red");
testElementInsertionBefore(subject_descendant, "red");
testElementInsertionAfter(subject_ancestor, "grey");
testElementInsertionAfter(subject_parent, "blue");
testElementInsertionAfter(subject, "green");
testElementInsertionAfter(subject_child, "red");
testElementInsertionAfter(subject_descendant, "red");
testTreeInsertionBefore(subject_ancestor, "grey");
testTreeInsertionBefore(subject_parent, "grey");
testTreeInsertionBefore(subject, "red");
testTreeInsertionBefore(subject_child, "red");
testTreeInsertionBefore(subject_descendant, "red");
testTreeInsertionAfter(subject_ancestor, "grey");
testTreeInsertionAfter(subject_parent, "pink");
testTreeInsertionAfter(subject, "red");
testTreeInsertionAfter(subject_child, "red");
testTreeInsertionAfter(subject_descendant, "red");
</script>

View File

@@ -0,0 +1,312 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() in sibling position</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
div:has(.test) ~ #subject { color: red }
div:has([test_attr]) ~ #subject { color: orangered }
div:has(> .test) ~ #subject { color: green }
div:has(> [test_attr]) ~ #subject { color: lightgreen }
div:has(~ .test) ~ #subject { color: yellow }
div:has(~ [test_attr]) ~ #subject { color: ivory }
div:has(+ .test) ~ #subject { color: blue }
div:has(+ [test_attr]) ~ #subject { color: skyblue }
div:has(~ div .test) ~ #subject { color: purple }
div:has(~ div [test_attr]) ~ #subject { color: violet }
div:has(+ div .test) ~ #subject { color: pink }
div:has(+ div [test_attr]) ~ #subject { color: lightpink }
</style>
<main id=main>
<div id=previous_sibling>
<div id=previous_sibling_child>
<div id=previous_sibling_descendant></div>
</div>
</div>
<div id=subject></div>
<div id=next_sibling>
<div id=next_sibling_child>
<div id=next_sibling_descendant></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const orangered = 'rgb(255, 69, 0)';
const green = 'rgb(0, 128, 0)';
const lightgreen = 'rgb(144, 238, 144)';
const blue = 'rgb(0, 0, 255)';
const skyblue = 'rgb(135, 206, 235)';
const yellow = 'rgb(255, 255, 0)';
const ivory = 'rgb(255, 255, 240)';
const purple = 'rgb(128, 0, 128)';
const violet = 'rgb(238, 130, 238)';
const pink = 'rgb(255, 192, 203)';
const lightpink = 'rgb(255, 182, 193)';
const colors = {
grey: {
classTest: grey,
attributeTest: grey,
},
red: {
classTest: red,
attributeTest: orangered,
},
green: {
classTest: green,
attributeTest: lightgreen,
},
blue: {
classTest: blue,
attributeTest: skyblue,
},
yellow: {
classTest: yellow,
attributeTest: ivory,
},
purple: {
classTest: purple,
attributeTest: violet,
},
pink: {
classTest: pink,
attributeTest: lightpink,
},
};
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
element.classList.add('test');
testColor(`add .test to ${element.id}`, expectedColorForClassTest);
element.classList.remove('test');
testColor(`remove .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
beforeElement.before(newElement);
testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test before ${beforeElement.id}`, grey);
newElement.classList.remove('test')
beforeElement.before(newElement);
testColor(`insert element div before ${beforeElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove element div before ${beforeElement.id}`, grey);
newElement.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
newElement.classList.add('test')
afterElement.after(newElement);
testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove element div.test after ${afterElement.id}`, grey);
newElement.classList.remove('test');
afterElement.after(newElement);
testColor(`insert element div after ${afterElement.id}`, grey);
newElement.classList.add('test');
testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest);
newElement.classList.remove('test');
testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove element div after ${afterElement.id}`, grey);
newElement.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div[test_attr] after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
newChild.classList.remove('test');
beforeElement.before(newElement);
testColor(`insert tree div>div before ${beforeElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div before ${beforeElement.id}`, grey);
newChild.setAttribute('test_attr', '');
beforeElement.before(newElement);
testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
const expectedColorForAttributeTest = colors[expectedColorName].attributeTest;
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest);
newElement.remove();
testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
newChild.classList.remove('test');
afterElement.after(newElement);
testColor(`insert tree div>div after ${afterElement.id}`, grey);
newChild.classList.add('test');
testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest);
newChild.classList.remove('test');
testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey);
newElement.remove();
testColor(`remove tree div>div after ${afterElement.id}`, grey);
newChild.setAttribute('test_attr', '');
afterElement.after(newElement);
testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest);
newElement.remove();
testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(previous_sibling, "grey");
testClassChange(previous_sibling_child, "green");
testClassChange(previous_sibling_descendant, "red");
testClassChange(subject, "blue");
testClassChange(next_sibling, "yellow");
testClassChange(next_sibling_child, "purple");
testClassChange(next_sibling_descendant, "purple");
testElementInsertionBefore(previous_sibling, "grey");
testElementInsertionBefore(previous_sibling_child, "green");
testElementInsertionBefore(previous_sibling_descendant, "red");
testElementInsertionBefore(subject, "blue");
testElementInsertionBefore(next_sibling, "yellow");
testElementInsertionBefore(next_sibling_child, "purple");
testElementInsertionBefore(next_sibling_descendant, "purple");
testElementInsertionAfter(previous_sibling, "blue");
testElementInsertionAfter(previous_sibling_child, "green");
testElementInsertionAfter(previous_sibling_descendant, "red");
testElementInsertionAfter(subject, "yellow");
testElementInsertionAfter(next_sibling, "yellow");
testElementInsertionAfter(next_sibling_child, "purple");
testElementInsertionAfter(next_sibling_descendant, "purple");
testTreeInsertionBefore(previous_sibling, "green");
testTreeInsertionBefore(previous_sibling_child, "red");
testTreeInsertionBefore(previous_sibling_descendant, "red");
testTreeInsertionBefore(subject, "pink");
testTreeInsertionBefore(next_sibling, "purple");
testTreeInsertionBefore(next_sibling_child, "purple");
testTreeInsertionBefore(next_sibling_descendant, "purple");
testTreeInsertionAfter(previous_sibling, "pink");
testTreeInsertionAfter(previous_sibling_child, "red");
testTreeInsertionAfter(previous_sibling_descendant, "red");
testTreeInsertionAfter(subject, "purple");
testTreeInsertionAfter(next_sibling, "purple");
testTreeInsertionAfter(next_sibling_child, "purple");
testTreeInsertionAfter(next_sibling_descendant, "purple");
</script>

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has() invalidation after removing non-first element</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#subject:has(descendant) { color: red }
</style>
<main id="main">
<div id="subject">
<div></div>
<descendant id="descendant"></descendant>
</div>
</main>
<script>
let grey = 'rgb(128, 128, 128)';
let red = 'rgb(255, 0, 0)';
function test_div(test_name, el, color) {
test(function() {
assert_equals(getComputedStyle(el).color, color);
}, test_name + ': div#' + el.id + '.color');
}
test_div('initial_color', subject, red);
subject.removeChild(descendant);
test_div('remove descendant', subject, grey);
</script>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has() invalidation for wiping an element by means of innerHTML</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
.subject:has(.descendant) { color: green}
</style>
<main id=main>
<div id="subject" class="subject"></div>
</main>
<script>
let grey = 'rgb(128, 128, 128)';
let green = 'rgb(0, 128, 0)';
function test_div(test_name, el, color) {
test(function() {
assert_equals(getComputedStyle(el).color, color);
}, test_name + ': div#' + el.id + '.color');
}
test_div('initial color', subject, grey);
subject.innerHTML = "This is a text <div><div class='descendant'></div></div>";
test_div('color after inserting text and div > .descendant', subject, green);
subject.innerHTML = "This is a text";
test_div('color after wiping #child to remove div > .descendant', subject, grey);
subject.innerHTML = "<div id='child'> This is a text <div class='descendant'></div></div>";
test_div('color after inserting text and #child > .descendant', subject, green);
child.innerHTML = "This is a text";
test_div('color after wiping #child to remove .descendant', subject, grey);
</script>

View File

@@ -0,0 +1,209 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>:has() invalidation for sibling insertion and removal</title>
<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#subject1:has(+ #sibling1_1 + #sibling1_2) { color: red }
#subject2:has(+ #sibling2_2) { color: green }
#subject3:has(+ #sibling3_1 + #sibling3_2 > #siblingchild3_2_1) { color: blue }
#subject4:has(+ #sibling4_2 > #siblingchild4_2_1) { color: yellow }
#subject5:has(+ #sibling5_1 + #sibling5_2) { color: red }
#subject6:has(+ #sibling6_2) { color: green }
#subject7:has(+ #sibling7_1 + #sibling7_2 > #siblingchild7_2_1) { color: blue }
#subject8:has(+ #sibling8_2 > #siblingchild8_2_1) { color: yellow }
#subject9:has(+ #sibling9_1 + #sibling9_2 ~ #sibling9_3) { color: red }
#subject10:has(+ #sibling10_2 ~ #sibling10_3) { color: green }
#subject11:has(+ #sibling11_1 + #sibling11_2 ~ #sibling11_3 > #siblingchild11_3_1) { color: blue }
#subject12:has(+ #sibling12_2 ~ #sibling12_3 > #siblingchild12_3_1) { color: yellow }
.sibling13:has(~ .sibling13) { color: green }
#subject14:has(~ #sibling14_1 ~ #sibling14_2 ~ #sibling14_3) { color: green }
</style>
<main id="main">
<div id="parent1">
<div id="subject1"></div>
<div id="sibling1_2"></div>
<div id="sibling1_3"></div>
</div>
<div id="parent2">
<div id="subject2"></div>
<div id="sibling2_1"></div>
<div id="sibling2_2"></div>
<div id="sibling2_3""></div>
</div>
<div id="parent3">
<div id="subject3"></div>
<div id="sibling3_2">
<div id="siblingchild3_2_1"></div>
</div>
<div id="sibling3_3"></div>
</div>
<div id="parent4">
<div id="subject4"></div>
<div id="sibling4_1"></div>
<div id="sibling4_2">
<div id="siblingchild4_2_1"></div>
</div>
<div id="sibling4_3"></div>
</div>
<div id="parent5">
<div id="subject5"></div>
<div id="sibling5_1"></div>
<div id="sibling5_2"></div>
<div id="sibling5_3""></div>
</div>
<div id="parent6">
<div id="subject6"></div>
<div id="sibling6_2"></div>
<div id="sibling6_3"></div>
</div>
<div id="parent7">
<div id="subject7"></div>
<div id="sibling7_1"></div>
<div id="sibling7_2">
<div id="siblingchild7_2_1"></div>
</div>
<div id="sibling7_3"></div>
<div id="parent8">
<div id="subject8"></div>
<div id="sibling8_2">
<div id="siblingchild8_2_1"></div>
</div>
<div id="sibling8_3"></div>
</div>
<div id="parent9">
<div id="subject9"></div>
<div id="sibling9_2"></div>
<div id="sibling9_3"></div>
<div id="sibling9_4"></div>
</div>
<div id="parent10">
<div id="subject10"></div>
<div id="sibling10_1"></div>
<div id="sibling10_2"></div>
<div id="sibling10_3""></div>
<div id="sibling10_4""></div>
</div>
<div id="parent11">
<div id="subject11"></div>
<div id="sibling11_2"></div>
<div id="sibling11_3">
<div id="siblingchild11_3_1"></div>
</div>
<div id="sibling11_4"></div>
</div>
<div id="parent12">
<div id="subject12"></div>
<div id="sibling12_1"></div>
<div id="sibling12_2"></div>
<div id="sibling12_3">
<div id="siblingchild12_3_1"></div>
</div>
<div id="sibling12_4"></div>
</div>
<div id="parent13">
<div class="sibling13"></div>
<div id="subject13" class="sibling13"></div>
</div>
<div id="parent14">
<div id="subject14"></div>
<div id="sibling14_1"></div>
<div id="sibling14_2"></div>
<div id="sibling14_3"></div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
const yellow = 'rgb(255, 255, 0)';
function testColor(test_name, element, color) {
test(function() {
assert_equals(getComputedStyle(element).color, color);
}, test_name);
}
function insertBefore(parent, reference, child_id) {
var child = document.createElement("div");
child.id = child_id;
parent.insertBefore(child, reference);
}
testColor(`subject1: initial color should be ${grey}`, subject1, grey);
insertBefore(parent1, sibling1_2, "sibling1_1");
testColor(`subject1: color after #sibling1_1 inserted should be ${red}`,
subject1, red);
testColor(`subject2: initial color should be ${grey}`, subject2, grey);
sibling2_1.remove();
testColor(`subject2: color after #sibling2_1 removed should be ${green}`,
subject2, green);
testColor(`subject3: initial color should be ${grey}`, subject3, grey);
insertBefore(parent3, sibling3_2, "sibling3_1");
testColor(`subject3: color after #sibling3_1 inserted should be ${blue}`,
subject3, blue);
testColor(`subject4: initial color should be ${grey}`, subject4, grey);
sibling4_1.remove();
testColor(`subject4: color after #sibling4_1 removed should be ${yellow}`,
subject4, yellow);
testColor(`subject5: initial color should be ${red}`, subject5, red);
sibling5_1.remove();
testColor(`subject5: color after #sibling5_1 removed should be ${grey}`,
subject5, grey);
testColor(`subject6: initial color should be ${green}`, subject6, green);
insertBefore(parent6, sibling6_2, "sibling6_1");
testColor(`subject6: color after #sibling6_1 inserted should be ${grey}`,
subject6, grey);
testColor(`subject7: initial color should be ${blue}`, subject7, blue);
sibling7_1.remove();
testColor(`subject7: color after #sibling7_1 removed should be ${grey}`,
subject7, grey);
testColor(`subject8: initial color should be ${yellow}`, subject8, yellow);
insertBefore(parent8, sibling8_2, "sibling8_1");
testColor(`subject8: color after #sibling8_1 inserted should be ${grey}`,
subject8, grey);
testColor(`subject9: initial color should be ${grey}`, subject9, grey);
insertBefore(parent9, sibling9_2, "sibling9_1");
testColor(`subject9: color after #sibling9_1 inserted should be ${red}`,
subject1, red);
testColor(`subject10: initial color should be ${grey}`, subject10, grey);
sibling10_1.remove();
testColor(`subject10: color after #sibling10_1 removed should be ${green}`,
subject10, green);
testColor(`subject11: initial color should be ${grey}`, subject11, grey);
insertBefore(parent11, sibling11_2, "sibling11_1");
testColor(`subject11: color after #sibling11_1 inserted should be ${blue}`,
subject11, blue);
testColor(`subject12: initial color should be ${grey}`, subject12, grey);
sibling12_1.remove();
testColor(`subject12: color after #sibling12_1 removed should be ${yellow}`,
subject12, yellow);
testColor(`subject13: initial color should be ${grey}`, subject13, grey);
const d = document.createElement("div");
d.classList.add("sibling13");
parent13.appendChild(d);
testColor(`subject13: color after #sibling12_1 removed should be ${green}`,
subject13, green);
testColor(`subject14: initial color should be ${green}`, subject14, green);
sibling14_2.remove();
testColor(`subject14: color after #sibling14_2 removed should be ${grey}`, subject14, grey);
</script>

View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() with sibling combinator argument</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#subject:has(~ .test) { color: red }
#subject:has(+ .test) { color: green }
#subject:has(~ div .test) { color: blue }
#subject:has(~ div > .test) { color: purple }
#subject:has(+ div .test) { color: yellow }
#subject:has(+ div > .test) { color: pink }
</style>
<main id=main>
<div id=subject></div>
<div id=first_sibling>
<div id=first_sibling_child>
<div id=first_sibling_descendant></div>
</div>
</div>
<div id=second_sibling></div>
<div id=third_sibling>
<div id=third_sibling_child>
<div id=third_sibling_descendant></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
const yellow = 'rgb(255, 255, 0)';
const purple = 'rgb(128, 0, 128)';
const pink = 'rgb(255, 192, 203)';
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColor)
{
element.classList.add('test');
testColor(`add .test to ${element.id}`, expectedColor);
element.classList.remove('test');
testColor(`remove .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColor)
{
const newElement = document.createElement('div');
newElement.classList.add('test')
beforeElement.before(newElement);
testColor(`insert element div.test before ${beforeElement.id}`, expectedColor);
newElement.remove();
testColor(`remove element div.test before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColor)
{
const newElement = document.createElement('div');
newElement.classList.add('test')
afterElement.after(newElement);
testColor(`insert element div.test after ${afterElement.id}`, expectedColor);
newElement.remove();
testColor(`remove element div.test after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColor)
{
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColor);
newElement.remove();
testColor(`remove tree div>div.test before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColor)
{
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newChild.classList.add('test');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColor);
newElement.remove();
testColor(`remove tree div>div.test after ${afterElement.id}`, grey);
}
testColor('initial_color', grey);
testClassChange(first_sibling, green);
testClassChange(second_sibling, red);
testClassChange(third_sibling, red);
testClassChange(first_sibling_child, pink);
testClassChange(first_sibling_descendant, yellow);
testClassChange(third_sibling_child, purple);
testClassChange(third_sibling_descendant, blue);
testElementInsertionBefore(first_sibling, green);
testElementInsertionBefore(second_sibling, red);
testElementInsertionBefore(third_sibling, red);
testElementInsertionBefore(first_sibling_child, pink);
testElementInsertionBefore(first_sibling_descendant, yellow);
testElementInsertionBefore(third_sibling_child, purple);
testElementInsertionBefore(third_sibling_descendant, blue);
testElementInsertionAfter(first_sibling, red);
testElementInsertionAfter(second_sibling, red);
testElementInsertionAfter(third_sibling, red);
testElementInsertionAfter(first_sibling_child, pink);
testElementInsertionAfter(first_sibling_descendant, yellow);
testElementInsertionAfter(third_sibling_child, purple);
testElementInsertionAfter(third_sibling_descendant, blue);
testTreeInsertionBefore(first_sibling, pink);
testTreeInsertionBefore(second_sibling, purple);
testTreeInsertionBefore(third_sibling, purple);
testTreeInsertionBefore(first_sibling_child, yellow);
testTreeInsertionBefore(first_sibling_descendant, yellow);
testTreeInsertionBefore(third_sibling_child, blue);
testTreeInsertionBefore(third_sibling_descendant, blue);
testTreeInsertionAfter(first_sibling, purple);
testTreeInsertionAfter(second_sibling, purple);
testTreeInsertionAfter(third_sibling, purple);
testTreeInsertionAfter(first_sibling_child, yellow);
testTreeInsertionAfter(first_sibling_descendant, yellow);
testTreeInsertionAfter(third_sibling_child, blue);
testTreeInsertionAfter(third_sibling_descendant, blue);
</script>

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: Invalidate :has() as result of insertion/removal</title>
<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#subject:has(+ #next_sibling) { color: red; }
#prev_sibling:has(+ #subject + #next_sibling) { color: green; }
</style>
<main id=main>
<div id=prev_sibling></div>
<div id=subject></div>
<div id=blocks_match></div>
<div id=next_sibling></div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
function testColors(test_name, subject_color, prev_sibling_color) {
test(function() {
assert_equals(getComputedStyle(subject).color, subject_color);
assert_equals(getComputedStyle(prev_sibling).color, prev_sibling_color);
}, test_name);
}
testColors('Initial colors', grey, grey);
const d = blocks_match;
d.remove();
testColors('Matches after #blocks_match removed', red, green);
subject.after(d);
testColors('Does not match after #blocks_match added', grey, grey);
</script>

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() affected by unstyled elements</title>
<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
.none { display: none; }
#subject:has(.test) { color: red; }
#subject:has(~ #sibling .test) { color: green; }
#subject:has(:is(.test_inner #subject_descendant)) { color: blue; }
#subject:has(~ #sibling :is(.test_inner #sibling_descendant)) { color: yellow; }
</style>
<main id=main>
<div id=subject>
<div id=subject_child class="none">
<div id=subject_descendant></div>
</div>
</div>
<div id=sibling class="none">
<div id=sibling_child>
<div id=sibling_descendant></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
const yellow = "rgb(255, 255, 0)";
const colors = {
grey: {
classTest: grey,
},
red: {
classTest: red,
},
green: {
classTest: green,
},
blue: {
classTest: blue,
},
yellow: {
classTest: yellow,
},
};
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, cls, expectedColorName)
{
const expectedColorForClassTest = colors[expectedColorName].classTest;
element.classList.add(cls);
testColor(`add ${cls} to ${element.id}`, expectedColorForClassTest);
element.classList.remove(cls);
testColor(`remove ${cls} from ${element.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(subject_descendant, 'test', 'red');
testClassChange(sibling_descendant, 'test', 'green');
testClassChange(subject_child, 'test_inner', 'blue');
testClassChange(sibling_child, 'test_inner', 'yellow');
</script>

View File

@@ -0,0 +1,158 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() with nesting parent containing complex selector</title>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../resources/testdriver.js"></script>
<script src="../../../resources/testdriver-actions.js"></script>
<script src="../../../resources/testdriver-vendor.js"></script>
<style>
.anchor { background-color: white; }
.ancestor .descendant {
.anchor:has(&) { background-color: blue; }
}
.ancestor .child {
.anchor:has(> &) { background-color: lightblue; }
}
.ancestor_prev ~ div .descendant {
.anchor:has(&) { background-color: yellow; }
}
.ancestor_prev ~ div.ancestor .descendant {
.anchor:has(&) { background-color: yellowgreen; }
}
.prev ~ .indirect_next {
.anchor:has(~ &) { background-color: green; }
}
.prev ~ .direct_next {
.anchor:has(+ &) { background-color: lightgreen; }
}
</style>
<div><div id="grand_parent1">
<div id="parent1">
<div id="anchor1" class="anchor">
<div><div class="descendant"></div></div>
</div>
</div>
</div></div>
<div><div id="grand_parent2">
<div id="parent2">
<div id="anchor2" class="anchor">
<div class="child"></div>
</div>
</div>
</div></div>
<div><div id="grand_parent_indirect_prev3"></div>
<div id="grand_parent_direct_prev3"></div>
<div id="grand_parent3">
<div id="parent_indirect_prev3"></div>
<div id="parent_direct_prev3"></div>
<div id="parent3">
<div id="anchor_indirect_prev3"></div>
<div id="anchor_direct_prev3"></div>
<div id="anchor3" class="anchor">
<div><div class="descendant"></div></div>
</div>
</div>
</div></div>
<div><div id="indirect_prev4"></div>
<div id="direct_prev4"></div>
<div id="anchor4" class="anchor"></div>
<div></div><div class="indirect_next">
</div></div>
<div><div id="indirect_prev5"></div>
<div id="direct_prev5"></div>
<div id="anchor5" class="anchor"></div>
<div class="direct_next">
</div></div>
<script>
const white = "rgb(255, 255, 255)";
const blue = "rgb(0, 0, 255)";
const lightblue = "rgb(173, 216, 230)";
const yellow = "rgb(255, 255, 0)";
const yellowgreen = "rgb(154, 205, 50)";
const green = "rgb(0, 128, 0)";
const lightgreen = "rgb(144, 238, 144)";
function bg_color(element, color, message) {
promise_test(async () => {
assert_equals(getComputedStyle(element)['background-color'], color);
}, message);
}
function add_class_and_check_bg_color(
element_to_add, class_name, has_anchor, color) {
promise_test(async () => {
element_to_add.classList.add(class_name);
assert_equals(getComputedStyle(has_anchor)['background-color'], color);
}, `#${has_anchor.id} becomes ${color} after adding .${class_name} to #${element_to_add.id}`);
}
function remove_class_and_check_bg_color(
element_to_remove, class_name, has_anchor, color) {
promise_test(async () => {
element_to_remove.classList.remove(class_name);
assert_equals(getComputedStyle(has_anchor)['background-color'], color);
}, `#${has_anchor.id} becomes ${color} after removing .${class_name} from #${element_to_remove.id}`);
}
bg_color(anchor1, white, "#anchor1 initially white");
add_class_and_check_bg_color(grand_parent1, "ancestor", anchor1, blue);
remove_class_and_check_bg_color(grand_parent1, "ancestor", anchor1, white);
add_class_and_check_bg_color(parent1, "ancestor", anchor1, blue);
remove_class_and_check_bg_color(parent1, "ancestor", anchor1, white);
bg_color(anchor2, white, "#anchor2 initially white");
add_class_and_check_bg_color(grand_parent2, "ancestor", anchor2, lightblue);
remove_class_and_check_bg_color(grand_parent2, "ancestor", anchor2, white);
add_class_and_check_bg_color(parent2, "ancestor", anchor2, lightblue);
remove_class_and_check_bg_color(parent2, "ancestor", anchor2, white);
bg_color(anchor3, white, "#anchor3 initially white");
add_class_and_check_bg_color(grand_parent_indirect_prev3, "ancestor_prev",
anchor3, yellow);
add_class_and_check_bg_color(grand_parent3, "ancestor", anchor3, yellowgreen);
remove_class_and_check_bg_color(grand_parent3, "ancestor", anchor3, yellow);
remove_class_and_check_bg_color(grand_parent_indirect_prev3, "ancestor_prev",
anchor3, white);
add_class_and_check_bg_color(grand_parent_direct_prev3, "ancestor_prev",
anchor3, yellow);
remove_class_and_check_bg_color(grand_parent_direct_prev3, "ancestor_prev",
anchor3, white);
add_class_and_check_bg_color(parent_indirect_prev3, "ancestor_prev",
anchor3, yellow);
add_class_and_check_bg_color(parent3, "ancestor", anchor3, yellowgreen);
remove_class_and_check_bg_color(parent3, "ancestor", anchor3, yellow);
remove_class_and_check_bg_color(parent_indirect_prev3, "ancestor_prev",
anchor3, white);
add_class_and_check_bg_color(parent_direct_prev3, "ancestor_prev",
anchor3, yellow);
remove_class_and_check_bg_color(parent_direct_prev3, "ancestor_prev",
anchor3, white);
add_class_and_check_bg_color(anchor_indirect_prev3, "ancestor_prev",
anchor3, yellow);
remove_class_and_check_bg_color(anchor_indirect_prev3, "ancestor_prev",
anchor3, white);
add_class_and_check_bg_color(anchor_direct_prev3, "ancestor_prev",
anchor3, yellow);
remove_class_and_check_bg_color(anchor_direct_prev3, "ancestor_prev",
anchor3, white);
bg_color(anchor4, white, "#anchor4 initially white");
add_class_and_check_bg_color(indirect_prev4, "prev", anchor4, green);
remove_class_and_check_bg_color(indirect_prev4, "prev", anchor4, white);
add_class_and_check_bg_color(direct_prev4, "prev", anchor4, green);
remove_class_and_check_bg_color(direct_prev4, "prev", anchor4, white);
bg_color(anchor5, white, "#anchor5 initially white");
add_class_and_check_bg_color(indirect_prev5, "prev", anchor5, lightgreen);
remove_class_and_check_bg_color(indirect_prev5, "prev", anchor5, white);
add_class_and_check_bg_color(direct_prev5, "prev", anchor5, lightgreen);
remove_class_and_check_bg_color(direct_prev5, "prev", anchor5, white);
</script>

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() with nesting parent containing :hover</title>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../resources/testdriver.js"></script>
<script src="../../../resources/testdriver-actions.js"></script>
<script src="../../../resources/testdriver-vendor.js"></script>
<style>
dd, dt { background: white; }
dd:hover {
dt:has(~ &) { background: lime; }
}
</style>
<dt id=dt1>#dt1</dt>
<dd id=dd1>#dd1, Hover me, the DT above should go lime</dd>
<dt id=dt2>#dt2</dt>
<dd id=dd2>#dd2, Hover me, both DTs above should go lime</dd>
<script>
const white = 'rgb(255, 255, 255)';
const lime = 'rgb(0, 255, 0)';
function bg_color(element, color, message) {
assert_equals(getComputedStyle(element)['background-color'], color, message);
}
promise_test(async () => {
bg_color(dt1, white, "#dt1 initially white");
bg_color(dd1, white, "#dd1 initially white");
bg_color(dt2, white, "#dt2 initially white");
bg_color(dd2, white, "#dd2 initially white");
await new test_driver.Actions().pointerMove(0, 0, {origin: dd1}).send();
bg_color(dt1, lime, "#dt1 should go lime when hover #dd1");
bg_color(dd1, white, "#dd1 doesn't change when hover #dd1");
bg_color(dt2, white, "#dt2 doesn't change when hover #dd1");
bg_color(dd2, white, "#dd2 doesn't change when hover #dd1");
await new test_driver.Actions().pointerMove(0, 0, {origin: dt1}).send();
bg_color(dt1, white, "#dt1 should go white when hover #dt2");
bg_color(dd1, white, "#dd1 doesn't change when hover #dt2");
bg_color(dt2, white, "#dt2 doesn't change when hover #dt2");
bg_color(dd2, white, "#dd2 doesn't change when hover #dt2");
await new test_driver.Actions().pointerMove(0, 0, {origin: dd2}).send();
bg_color(dt1, lime, "#dt1 should go lime when hover #dd2");
bg_color(dd1, white, "#dd1 doesn't change when hover #dd2");
bg_color(dt2, lime, "#dt2 should go lime when hover #dd2");
bg_color(dd2, white, "#dd2 doesn't change when hover #dd2");
});
</script>

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() with :not()</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
div, main { color: grey }
#subject:has(:not(.test)) { color: green }
#subject:has(.test :not(.test)) { color: red }
</style>
<main id=main>
<div id=subject>
<div id=subject_child class=test>
<div id=subject_descendant class=test></div>
</div>
</div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
function testColor(test_name, color) {
test(function() {
assert_equals(getComputedStyle(subject).color, color);
}, test_name);
}
function testClassChange(element, expectedColor)
{
element.classList.remove('test');
testColor(`remove .test to ${element.id}`, expectedColor);
element.classList.add('test');
testColor(`add .test from ${element.id}`, grey);
}
function testElementInsertionBefore(beforeElement, expectedColor)
{
const newElement = document.createElement('div');
beforeElement.before(newElement);
testColor(`insert element div before ${beforeElement.id}`, expectedColor);
newElement.remove();
testColor(`remove element div before ${beforeElement.id}`, grey);
}
function testElementInsertionAfter(afterElement, expectedColor)
{
const newElement = document.createElement('div');
afterElement.after(newElement);
testColor(`insert element div after ${afterElement.id}`, expectedColor);
newElement.remove();
testColor(`remove element div after ${afterElement.id}`, grey);
}
function testTreeInsertionBefore(beforeElement, expectedColor)
{
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newElement.appendChild(newChild);
beforeElement.before(newElement);
testColor(`insert tree div>div before ${beforeElement.id}`, expectedColor);
newElement.remove();
testColor(`remove tree div>div before ${beforeElement.id}`, grey);
}
function testTreeInsertionAfter(afterElement, expectedColor)
{
const newElement = document.createElement('div');
const newChild = document.createElement('div');
newElement.appendChild(newChild);
afterElement.after(newElement);
testColor(`insert tree div.test after ${afterElement.id}`, expectedColor);
newElement.remove();
testColor(`remove tree div.test after ${afterElement.id}`, grey);
}
testColor('Initial color', grey);
testClassChange(subject_child, green);
testClassChange(subject_descendant, red);
testElementInsertionBefore(subject_child, green);
testElementInsertionBefore(subject_descendant, red);
testElementInsertionAfter(subject_child, green);
testElementInsertionAfter(subject_descendant, red);
testTreeInsertionBefore(subject_child, green);
testTreeInsertionBefore(subject_descendant, red);
testTreeInsertionAfter(subject_child, green);
testTreeInsertionAfter(subject_descendant, red);
</script>

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selectors Test: :has(:nth-child()) invalidation for sibling change</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
#test-container > div { background-color: green; }
#target1:has(.item:nth-child(3)) { background-color: red; }
#target2:has(.item:nth-last-child(3)) { background-color: red; }
#target3:has(.item:nth-child(3) > .child) { background-color: red; }
#target4:has(.item:nth-last-child(3) > .child) { background-color: red; }
</style>
<div id="test-container">
<div id="target1">
<div class="item" id="item1">FAIL if you see this text</div>
<div class="item"></div>
<div class="item">This text should have a green background</div>
</div>
<div id="target2">
<div class="item">This text should have a green background</div>
<div class="item"></div>
<div class="item" id="item2">FAIL if you see this text</div>
</div>
<div id="target3">
<div class="item" id="item3">FAIL if you see this text</div>
<div class="item"></div>
<div class="item">
<span class="child">This text should have a green background<span>
</div>
</div>
<div id="target4">
<div class="item">
<span class="child">This text should have a green background<span>
</div>
<div class="item"></div>
<div class="item" id="item4">FAIL if you see this text</div>
</div>
</div>
<script>
test(() => {
assert_equals(getComputedStyle(target1).backgroundColor, "rgb(255, 0, 0)");
assert_equals(getComputedStyle(target2).backgroundColor, "rgb(255, 0, 0)");
assert_equals(getComputedStyle(target3).backgroundColor, "rgb(255, 0, 0)");
assert_equals(getComputedStyle(target4).backgroundColor, "rgb(255, 0, 0)");
}, "Initially red");
test(() => {
item1.remove();
assert_equals(getComputedStyle(target1).backgroundColor, "rgb(0, 128, 0)");
}, ":nth-child() no longer matching after removal");
test(() => {
item2.remove();
assert_equals(getComputedStyle(target2).backgroundColor, "rgb(0, 128, 0)");
}, ":nth-last-child() no longer matching after removal");
test(() => {
item3.remove();
assert_equals(getComputedStyle(target3).backgroundColor, "rgb(0, 128, 0)");
}, ":nth-child() in non-subject no longer matching after removal");
test(() => {
item4.remove();
assert_equals(getComputedStyle(target4).backgroundColor, "rgb(0, 128, 0)");
}, ":nth-last-child() in non-subject no longer matching after removal");
</script>

View File

@@ -0,0 +1,99 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() with pseudo-classes</title>
<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<style>
main:has(input) div { color: grey }
main:has(#checkbox:checked) > #subject { color: red }
main:has(#option:checked) > #subject { color: red }
main:has(#checkbox:disabled) > #subject { color: green }
main:has(#option:disabled) > :is(#subject, #subject2) { color: green }
main:has(#optgroup:disabled) > #subject { color: blue }
main:not(:has(#checkbox:enabled)) > #subject3 { color: green }
main:not(:has(#option:enabled)) :is(#subject3, #subject4) { color: green }
main:not(:has(#optgroup:enabled)) > #subject3 { color: blue }
main:has(#text_input:valid) > #subject { color: yellow }
main:not(:has(#text_input:invalid)) > #subject2 { color: yellow }
main:has(#form:valid) > #subject3 { color: yellow }
main:not(:has(#form:invalid)) > #subject4 { color: yellow }
</style>
<main id=main>
<form id=form>
<input type=checkbox id=checkbox></input>
<select id=select><optgroup id=optgroup><option>a</option><option id=option>b</option></optgroup></select>
<input id=text_input type=text required></input>
</form>
<div id=subject></div>
<div id=subject2></div>
<div id=subject3></div>
<div id=subject4></div>
</main>
<script>
const grey = 'rgb(128, 128, 128)';
const red = 'rgb(255, 0, 0)';
const green = 'rgb(0, 128, 0)';
const blue = 'rgb(0, 0, 255)';
const yellow = 'rgb(255, 255, 0)';
const purple = 'rgb(128, 0, 128)';
const pink = 'rgb(255, 192, 203)';
function testColor(test_name, subject_element, color) {
test(function() {
assert_equals(getComputedStyle(subject_element).color, color);
}, test_name);
}
function testPseudoClassChange(element, property, subject_element, expectedColor)
{
testColor(`Before set ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, grey);
element[property] = true;
testColor(`Set ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, expectedColor);
element[property] = false;
testColor(`Unset ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, grey);
}
function testSelectedChange(option, subject_element, expectedColor)
{
const oldOption = select.selectedOptions[0];
option.selected = true;
testColor(`Set select on ${option.id}`, subject_element, expectedColor);
oldOption.selected = true;
testColor(`Reset select`, subject, grey);
}
function testValueChange(input, subject_element, expectedColor)
{
testColor(`Before setting value of ${input.id}, testing ${subject_element.id}`, subject_element, grey);
input.value = "value";
testColor(`Set value of ${input.id}, testing ${subject_element.id}`, subject_element, expectedColor);
input.value = "";
testColor(`Clear value of ${input.id}, testing ${subject_element.id}`, subject_element, grey);
}
testPseudoClassChange(checkbox, "checked", subject, red);
testSelectedChange(option, subject, red);
testPseudoClassChange(checkbox, "disabled", subject, green);
testPseudoClassChange(checkbox, "disabled", subject3, green);
testPseudoClassChange(option, "disabled", subject, green);
testPseudoClassChange(option, "disabled", subject3, green);
testPseudoClassChange(optgroup, "disabled", subject, blue);
testPseudoClassChange(optgroup, "disabled", subject2, green);
testPseudoClassChange(optgroup, "disabled", subject3, blue);
testPseudoClassChange(optgroup, "disabled", subject4, green);
testValueChange(text_input, subject, yellow);
testValueChange(text_input, subject2, yellow);
testValueChange(text_input, subject3, yellow);
testValueChange(text_input, subject4, yellow);
</script>