property-reflection-helper.js (11526B)
1 const Behavior = Object.freeze({ 2 ReflectsHost: 'ReflectsHost', 3 ReflectsHostReadOnly: 'ReflectsHostReadOnly', 4 ReflectsHostInArray: 'ReflectsHostInArray', 5 IsNull: 'IsNull', 6 ReflectsHostID: 'ReflectsHostID', 7 ReflectsHostIDInDOMTokenList: 'ReflectsHostIDInDOMTokenList', 8 }); 9 10 // We want to test types of elements that are associated with properties that can reflect other 11 // elements and can therefore interact with reference target in interesting ways. 12 // The HTML5_LABELABLE_ELEMENTS are defined in https://html.spec.whatwg.org/#category-label, 13 // while non_labelable_element_types is a manually curated list of other elements with 14 // reflecting properties (plus div as representative of more "normal" elements). 15 // We'll test all permutations of these element types being both the referencing element 16 // pointing into the reference target shadow host, and being the referenced element inside 17 // the shadow. 18 const non_labelable_element_types = ["div", "object", "label", "fieldset", "legend", "option", "datalist", "form"]; 19 const element_types = HTML5_LABELABLE_ELEMENTS.concat(non_labelable_element_types); 20 21 function test_property_reflection(element_creation_method, test_name_suffix, referencing_element_type, referenced_element_type, attribute, reflected_property, expected_behavior) { 22 // There's nothing to test if the referencing element type doesn't have the reflecting 23 // property. 24 if (!(reflected_property in document.createElement(referencing_element_type))) { 25 return; 26 } 27 28 test(function () { 29 const referencing_element = document.createElement(referencing_element_type); 30 document.body.appendChild(referencing_element); 31 referencing_element.setAttribute(attribute, "host-id"); 32 const host_container = document.querySelector("#host-container"); 33 const host = element_creation_method(host_container, referenced_element_type); 34 if (expected_behavior === Behavior.ReflectsHost || expected_behavior === Behavior.ReflectsHostReadOnly) { 35 assert_equals(referencing_element[reflected_property], host); 36 } else if (expected_behavior === Behavior.ReflectsHostInArray) { 37 assert_array_equals(referencing_element[reflected_property], [host]); 38 } else if (expected_behavior === Behavior.IsNull) { 39 assert_equals(referencing_element[reflected_property], null); 40 } else if (expected_behavior === Behavior.ReflectsHostID) { 41 assert_equals(referencing_element[reflected_property], "host-id"); 42 } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) { 43 assert_true(referencing_element[reflected_property] instanceof DOMTokenList); 44 assert_array_equals(Array.from(referencing_element[reflected_property]), ["host-id"]); 45 } 46 referencing_element.remove(); 47 host_container.setHTMLUnsafe(""); 48 }, `${referencing_element_type}.${reflected_property} has reflection behavior ${expected_behavior} when pointing to ${referenced_element_type} with reference target${test_name_suffix}`); 49 } 50 51 function test_idl_setter(element_creation_method, test_name_suffix, referencing_element_type, referenced_element_type, attribute, reflected_property, expected_behavior) { 52 // There's nothing to test if the referencing element type doesn't have the reflecting 53 // property. 54 if (!(reflected_property in document.createElement(referencing_element_type))) { 55 return; 56 } 57 58 test(function () { 59 const referencing_element = document.createElement(referencing_element_type); 60 document.body.appendChild(referencing_element); 61 const host_container = document.querySelector("#host-container"); 62 const host = element_creation_method(host_container, referenced_element_type); 63 64 if (reflected_property === "ariaOwnsElements") { 65 // It's undetermined whether reference target should work with aria-owns or not; see 66 // https://github.com/WICG/webcomponents/issues/1091 and 67 // https://github.com/w3c/aria/issues/2266 68 return; 69 } 70 71 if (expected_behavior === Behavior.ReflectsHost) { 72 referencing_element[reflected_property] = host; 73 // For element reflecting properties, the IDL getter should return null when the explicitly 74 // set element has an invalid reference target. 75 assert_equals(referencing_element[reflected_property], null); 76 } else if (expected_behavior === Behavior.ReflectsHostReadOnly) { 77 referencing_element[reflected_property] = host; 78 // Setting a read-only property has no effect. 79 assert_equals(referencing_element[reflected_property], null); 80 } else if (expected_behavior === Behavior.ReflectsHostInArray) { 81 referencing_element[reflected_property] = [ host ]; 82 // For element reflecting properties, the IDL getter should not return explicitly set elements 83 // if they have an invalid reference target. 84 assert_array_equals(referencing_element[reflected_property], []); 85 } else if (expected_behavior === Behavior.IsNull) { 86 referencing_element[reflected_property] = host; 87 assert_equals(referencing_element[reflected_property], null); 88 } else if (expected_behavior === Behavior.ReflectsHostID) { 89 referencing_element[reflected_property] = "host-id"; 90 // Properties reflecting the host ID return the ID even if it points to an element with 91 // an invalid reference target. 92 assert_equals(referencing_element[reflected_property], "host-id"); 93 } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) { 94 // Properties reflecting a DOMTokenList of IDs returns the IDs even if they point to an 95 // element with an invalid reference target. 96 referencing_element[reflected_property] = [ "host-id" ]; 97 assert_true(referencing_element[reflected_property] instanceof DOMTokenList); 98 assert_array_equals(Array.from(referencing_element[reflected_property]), [ "host-id" ]); 99 } 100 101 // Set the reference target to a valid value. 102 host.shadowRoot.referenceTarget = host.shadowRoot.querySelector(referenced_element_type).id; 103 104 if (expected_behavior === Behavior.ReflectsHost) { 105 // For element reflecting properties, if the reference target becomes valid for the explicitly 106 // set element, we should start returning that element. 107 assert_equals(referencing_element[reflected_property], host); 108 } else if (expected_behavior === Behavior.ReflectsHostReadOnly) { 109 assert_equals(referencing_element[reflected_property], null); 110 } else if (expected_behavior === Behavior.ReflectsHostInArray) { 111 // For element reflecting properties, if the reference target becomes valid for any of the 112 // explicitly set elements, we should start returning that element. 113 assert_array_equals(referencing_element[reflected_property], [host]); 114 } else if (expected_behavior === Behavior.IsNull) { 115 assert_equals(referencing_element[reflected_property], null); 116 } else if (expected_behavior === Behavior.ReflectsHostID) { 117 assert_equals(referencing_element[reflected_property], "host-id"); 118 } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) { 119 assert_array_equals(Array.from(referencing_element[reflected_property]), [ "host-id" ]); 120 } 121 122 referencing_element.remove(); 123 host_container.setHTMLUnsafe(""); 124 }, `${referencing_element_type}.${reflected_property} has IDL setter behavior ${expected_behavior} when pointing to ${referenced_element_type} with reference target${test_name_suffix}`); 125 } 126 127 function run_test_for_all_reflecting_properties(setup_function, test_function, test_name_suffix) { 128 for(let referencing_element_type of element_types) { 129 for(let referenced_element_type of element_types) { 130 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-controls", "ariaControlsElements", Behavior.ReflectsHostInArray); 131 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-activedescendant", "ariaActiveDescendantElement", Behavior.ReflectsHost); 132 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-describedby", "ariaDescribedByElements", Behavior.ReflectsHostInArray); 133 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-details", "ariaDetailsElements", Behavior.ReflectsHostInArray); 134 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-errormessage", "ariaErrorMessageElements", Behavior.ReflectsHostInArray); 135 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-flowto", "ariaFlowToElements", Behavior.ReflectsHostInArray); 136 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-labelledby", "ariaLabelledByElements", Behavior.ReflectsHostInArray); 137 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-owns", "ariaOwnsElements", Behavior.ReflectsHostInArray); 138 139 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "anchor", "anchorElement", Behavior.ReflectsHost); 140 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "commandfor", "commandForElement", Behavior.ReflectsHost); 141 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "popovertarget", "popoverTargetElement", Behavior.ReflectsHost); 142 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "interestfor", "interestForElement", Behavior.ReflectsHost); 143 144 const expected_htmlFor_property_behavior = (referencing_element_type == "output") ? Behavior.ReflectsHostIDInDOMTokenList : Behavior.ReflectsHostID; 145 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "for", "htmlFor", expected_htmlFor_property_behavior); 146 147 // The form property of <label>, <legend>, and <option> reflects the form property of the associated labelable element, 148 // the associated <fieldset>, and the associated <select>, respectively. Here since we don't have those associated elements, 149 // the form property would return null. 150 const expected_form_property_behavior = (referenced_element_type == 'form' && 151 referencing_element_type != "label" && 152 referencing_element_type != "legend" && 153 referencing_element_type != "option") ? Behavior.ReflectsHostReadOnly : Behavior.IsNull; 154 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "form", "form", expected_form_property_behavior); 155 156 const expected_list_property_behavior = (referenced_element_type == 'datalist') ? Behavior.ReflectsHostReadOnly : Behavior.IsNull; 157 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "list", "list", expected_list_property_behavior); 158 159 const expected_control_property_behavior = HTML5_LABELABLE_ELEMENTS.includes(referenced_element_type) ? Behavior.ReflectsHostReadOnly : Behavior.IsNull; 160 test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "for", "control", expected_control_property_behavior); 161 } 162 } 163 }