reactions.js (26545B)
1 let testNumber = 1; 2 3 function testNodeConnector(testFunction, name) { 4 let container = document.createElement('div'); 5 container.appendChild(document.createElement('div')); 6 document.body.appendChild(container); 7 8 test(function () { 9 var element = define_new_custom_element(); 10 var instance = document.createElement(element.name); 11 assert_array_equals(element.takeLog().types(), ['constructed']); 12 testFunction(container, instance); 13 assert_array_equals(element.takeLog().types(), ['connected']); 14 }, name + ' must enqueue a connected reaction'); 15 16 test(function () { 17 var element = define_new_custom_element(); 18 var instance = document.createElement(element.name); 19 assert_array_equals(element.takeLog().types(), ['constructed']); 20 var newDoc = document.implementation.createHTMLDocument(); 21 testFunction(container, instance); 22 assert_array_equals(element.takeLog().types(), ['connected']); 23 testFunction(newDoc.documentElement, instance); 24 assert_array_equals(element.takeLog().types(), ['disconnected', 'adopted', 'connected']); 25 }, name + ' must enqueue a disconnected reaction, an adopted reaction, and a connected reaction when the custom element was in another document'); 26 27 container.parentNode.removeChild(container); 28 } 29 30 function testNodeDisconnector(testFunction, name) { 31 let container = document.createElement('div'); 32 container.appendChild(document.createElement('div')); 33 document.body.appendChild(container); 34 35 test(function () { 36 var element = define_new_custom_element(); 37 var instance = document.createElement(element.name); 38 assert_array_equals(element.takeLog().types(), ['constructed']); 39 container.appendChild(instance); 40 assert_array_equals(element.takeLog().types(), ['connected']); 41 testFunction(instance, window); 42 assert_array_equals(element.takeLog().types(), ['disconnected']); 43 }, name + ' must enqueue a disconnected reaction'); 44 45 container.parentNode.removeChild(container); 46 } 47 48 function testInsertingMarkup(testFunction, name) { 49 let container = document.createElement('div'); 50 container.appendChild(document.createElement('div')); 51 document.body.appendChild(container); 52 53 test(function () { 54 var element = define_new_custom_element(); 55 testFunction(container, `<${element.name}></${element.name}>`); 56 assert_array_equals(element.takeLog().types(), ['constructed', 'connected']); 57 }, name + ' must enqueue a connected reaction for a newly constructed custom element'); 58 59 test(function () { 60 var element = define_new_custom_element(['title']); 61 testFunction(container, `<${element.name} id="hello" title="hi"></${element.name}>`); 62 var logEntries = element.takeLog(); 63 assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged', 'connected']); 64 assert_attribute_log_entry(logEntries[1], {name: 'title', oldValue: null, newValue: 'hi', namespace: null}); 65 }, name + ' must enqueue a attributeChanged reaction for a newly constructed custom element'); 66 67 container.parentNode.removeChild(container); 68 } 69 70 function testParsingMarkup(testFunction, name) { 71 test(function () { 72 var element = define_new_custom_element(['id']); 73 assert_array_equals(element.takeLog().types(), []); 74 var instance = testFunction(document, `<${element.name} id="hello" class="foo"></${element.name}>`); 75 assert_equals(Object.getPrototypeOf(instance.querySelector(element.name)), element.class.prototype); 76 var logEntries = element.takeLog(); 77 assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged']); 78 assert_attribute_log_entry(logEntries[1], {name: 'id', oldValue: null, newValue: 'hello', namespace: null}); 79 }, name + ' must construct a custom element'); 80 } 81 82 function testCloner(testFunction, name) { 83 let container = document.createElement('div'); 84 container.appendChild(document.createElement('div')); 85 document.body.appendChild(container); 86 87 test(function () { 88 var element = define_new_custom_element(['id']); 89 var instance = document.createElement(element.name); 90 container.appendChild(instance); 91 92 instance.setAttribute('id', 'foo'); 93 assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']); 94 var newInstance = testFunction(instance); 95 var logEntries = element.takeLog(); 96 assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged']); 97 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'foo', namespace: null}); 98 }, name + ' must enqueue an attributeChanged reaction when cloning an element with an observed attribute'); 99 100 test(function () { 101 var element = define_new_custom_element(['id']); 102 var instance = document.createElement(element.name); 103 container.appendChild(instance); 104 105 instance.setAttribute('lang', 'en'); 106 assert_array_equals(element.takeLog().types(), ['constructed', 'connected']); 107 var newInstance = testFunction(instance); 108 assert_array_equals(element.takeLog().types(), ['constructed']); 109 }, name + ' must not enqueue an attributeChanged reaction when cloning an element with an unobserved attribute'); 110 111 test(function () { 112 var element = define_new_custom_element(['title', 'class']); 113 var instance = document.createElement(element.name); 114 container.appendChild(instance); 115 116 instance.setAttribute('lang', 'en'); 117 instance.className = 'foo'; 118 instance.setAttribute('title', 'hello world'); 119 assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged', 'attributeChanged']); 120 var newInstance = testFunction(instance); 121 var logEntries = element.takeLog(); 122 assert_array_equals(logEntries.types(), ['constructed', 'attributeChanged', 'attributeChanged']); 123 assert_attribute_log_entry(logEntries[1], {name: 'class', oldValue: null, newValue: 'foo', namespace: null}); 124 assert_attribute_log_entry(logEntries[2], {name: 'title', oldValue: null, newValue: 'hello world', namespace: null}); 125 }, name + ' must enqueue an attributeChanged reaction when cloning an element only for observed attributes'); 126 } 127 128 function testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, interfaceName, optionalSupportPredicate) { 129 test(function () { 130 if (optionalSupportPredicate) { 131 assert_implements_optional(optionalSupportPredicate()); 132 } 133 if (elementName === undefined) { 134 var element = define_new_custom_element([contentAttributeName]); 135 var instance = document.createElement(element.name); 136 } else { 137 var element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 138 var instance = document.createElement(elementName, { is: element.name }); 139 } 140 assert_array_equals(element.takeLog().types(), ['constructed']); 141 instance[jsAttributeName] = validValue1; 142 var logEntries = element.takeLog(); 143 assert_array_equals(logEntries.types(), ['attributeChanged']); 144 145 assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null}); 146 }, name + ' must enqueue an attributeChanged reaction when adding ' + contentAttributeName + ' content attribute'); 147 148 test(function () { 149 if (optionalSupportPredicate) { 150 assert_implements_optional(optionalSupportPredicate()); 151 } 152 if (elementName === undefined) { 153 var element = define_new_custom_element([contentAttributeName]); 154 var instance = document.createElement(element.name); 155 } else { 156 var element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 157 var instance = document.createElement(elementName, { is: element.name }); 158 } 159 instance[jsAttributeName] = validValue1; 160 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 161 instance[jsAttributeName] = validValue2; 162 var logEntries = element.takeLog(); 163 assert_array_equals(logEntries.types(), ['attributeChanged']); 164 assert_attribute_log_entry(logEntries.last(), {name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null}); 165 }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); 166 } 167 168 function testReflectAttribute(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, interfaceName, optionalSupportPredicate) { 169 testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, interfaceName, optionalSupportPredicate); 170 } 171 172 function testReflectBooleanAttribute(jsAttributeName, contentAttributeName, name, elementName, interfaceName) { 173 testReflectAttributeWithContentValues(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, interfaceName); 174 } 175 176 function testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, getParentElement, setAttributes, interfaceName) { 177 let parentElement = getParentElement(); 178 179 test(() => { 180 let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 181 let instance = document.createElement(elementName, { is: element.name }); 182 183 assert_array_equals(element.takeLog().types(), ['constructed']); 184 parentElement.appendChild(instance); 185 assert_array_equals(element.takeLog().types(), ['connected']); 186 setAttributes(instance); 187 instance[jsAttributeName] = validValue1; 188 let logEntries = element.takeLog(); 189 assert_array_equals(logEntries.types(), ['attributeChanged']); 190 assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null }); 191 192 }, name + ' must enqueue an attributeChanged reaction when adding a new attribute'); 193 194 test(() => { 195 let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 196 let instance = document.createElement(elementName, { is: element.name }); 197 parentElement.appendChild(instance); 198 setAttributes(instance); 199 instance[jsAttributeName] = validValue1; 200 201 assert_array_equals(element.takeLog().types(), ['constructed', 'connected', 'attributeChanged']); 202 instance[jsAttributeName] = validValue2; 203 let logEntries = element.takeLog(); 204 assert_array_equals(logEntries.types(), ['attributeChanged']); 205 assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null }); 206 207 }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); 208 } 209 210 function testReflectAttributeWithDependentAttributes(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, getParentElement, setAttributes, interfaceName) { 211 testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, getParentElement, setAttributes, interfaceName); 212 } 213 214 function testReflectBooleanAttributeWithDependentAttributes(jsAttributeName, contentAttributeName, name, elementName, getParentElement, setAttributes, interfaceName) { 215 testReflectAttributeWithContentValuesAndDependentAttributes(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, getParentElement, setAttributes, interfaceName); 216 } 217 218 function testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, validValue1, contentValue1, validValue2, contentValue2, name, elementName, getParentElement, interfaceName) { 219 let parentElement = getParentElement(); 220 221 test(() => { 222 let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 223 let instance = document.createElement(elementName, { is: element.name }); 224 225 assert_array_equals(element.takeLog().types(), ['constructed']); 226 parentElement.appendChild(instance); 227 assert_array_equals(element.takeLog().types(), ['connected']); 228 instance[jsAttributeName] = validValue1; 229 let logEntries = element.takeLog(); 230 assert_array_equals(logEntries.types(), ['attributeChanged']); 231 assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: null, newValue: contentValue1, namespace: null }); 232 }, name + ' must enqueue an attributeChanged reaction when adding a new attribute'); 233 234 test(() => { 235 let element = define_build_in_custom_element([contentAttributeName], interfaceName, elementName); 236 let instance = document.createElement(elementName, { is: element.name }); 237 parentElement.appendChild(instance); 238 239 assert_array_equals(element.takeLog().types(), ['constructed', 'connected']); 240 instance[jsAttributeName] = validValue1; 241 assert_array_equals(element.takeLog().types(), ['attributeChanged']); 242 instance[jsAttributeName] = validValue2; 243 let logEntries = element.takeLog(); 244 assert_array_equals(logEntries.types(), ['attributeChanged']); 245 assert_attribute_log_entry(logEntries.last(), { name: contentAttributeName, oldValue: contentValue1, newValue: contentValue2, namespace: null }); 246 }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); 247 } 248 249 function testReflectAttributeWithParentNode(jsAttributeName, contentAttributeName, validValue1, validValue2, name, elementName, getParentElement, interfaceName) { 250 testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, validValue1, validValue1, validValue2, validValue2, name, elementName, getParentElement, interfaceName); 251 } 252 253 function testReflectBooleanAttributeWithParentNode(jsAttributeName, contentAttributeName, name, elementName, getParentElement, interfaceName) { 254 testReflectAttributeWithContentValuesAndParentNode(jsAttributeName, contentAttributeName, true, '', false, null, name, elementName, getParentElement, interfaceName); 255 } 256 257 function testAttributeAdder(testFunction, name) { 258 test(function () { 259 var element = define_new_custom_element(['id']); 260 var instance = document.createElement(element.name); 261 assert_array_equals(element.takeLog().types(), ['constructed']); 262 testFunction(instance, 'id', 'foo'); 263 var logEntries = element.takeLog(); 264 assert_array_equals(logEntries.types(), ['attributeChanged']); 265 assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'foo', namespace: null}); 266 }, name + ' must enqueue an attributeChanged reaction when adding an attribute'); 267 268 test(function () { 269 var element = define_new_custom_element(['class']); 270 var instance = document.createElement(element.name); 271 assert_array_equals(element.takeLog().types(), ['constructed']); 272 testFunction(instance, 'data-lang', 'en'); 273 assert_array_equals(element.takeLog().types(), []); 274 }, name + ' must not enqueue an attributeChanged reaction when adding an unobserved attribute'); 275 276 test(function () { 277 var element = define_new_custom_element(['title']); 278 var instance = document.createElement(element.name); 279 instance.setAttribute('title', 'hello'); 280 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 281 testFunction(instance, 'title', 'world'); 282 var logEntries = element.takeLog(); 283 assert_array_equals(logEntries.types(), ['attributeChanged']); 284 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: 'world', namespace: null}); 285 }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); 286 287 test(function () { 288 var element = define_new_custom_element([]); 289 var instance = document.createElement(element.name); 290 instance.setAttribute('data-lang', 'zh'); 291 assert_array_equals(element.takeLog().types(), ['constructed']); 292 testFunction(instance, 'data-lang', 'en'); 293 assert_array_equals(element.takeLog().types(), []); 294 }, name + ' must enqueue an attributeChanged reaction when replacing an existing unobserved attribute'); 295 } 296 297 function testAttributeMutator(testFunction, name) { 298 test(function () { 299 var element = define_new_custom_element(['title']); 300 var instance = document.createElement(element.name); 301 instance.setAttribute('title', 'hello'); 302 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 303 testFunction(instance, 'title', 'world'); 304 var logEntries = element.takeLog(); 305 assert_array_equals(logEntries.types(), ['attributeChanged']); 306 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: 'world', namespace: null}); 307 }, name + ' must enqueue an attributeChanged reaction when replacing an existing attribute'); 308 309 test(function () { 310 var element = define_new_custom_element(['class']); 311 var instance = document.createElement(element.name); 312 instance.setAttribute('data-lang', 'zh'); 313 assert_array_equals(element.takeLog().types(), ['constructed']); 314 testFunction(instance, 'data-lang', 'en'); 315 assert_array_equals(element.takeLog().types(), []); 316 }, name + ' must not enqueue an attributeChanged reaction when replacing an existing unobserved attribute'); 317 } 318 319 function testAttributeRemover(testFunction, name, options) { 320 if (options && !options.onlyExistingAttribute) { 321 test(function () { 322 var element = define_new_custom_element(['title']); 323 var instance = document.createElement(element.name); 324 assert_array_equals(element.takeLog().types(), ['constructed']); 325 testFunction(instance, 'title'); 326 assert_array_equals(element.takeLog().types(), []); 327 }, name + ' must not enqueue an attributeChanged reaction when removing an attribute that does not exist'); 328 } 329 330 test(function () { 331 var element = define_new_custom_element([]); 332 var instance = document.createElement(element.name); 333 instance.setAttribute('data-lang', 'hello'); 334 assert_array_equals(element.takeLog().types(), ['constructed']); 335 testFunction(instance, 'data-lang'); 336 assert_array_equals(element.takeLog().types(), []); 337 }, name + ' must not enqueue an attributeChanged reaction when removing an unobserved attribute'); 338 339 test(function () { 340 var element = define_new_custom_element(['title']); 341 var instance = document.createElement(element.name); 342 instance.setAttribute('title', 'hello'); 343 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 344 testFunction(instance, 'title'); 345 var logEntries = element.takeLog(); 346 assert_array_equals(logEntries.types(), ['attributeChanged']); 347 assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: null, namespace: null}); 348 }, name + ' must enqueue an attributeChanged reaction when removing an existing attribute'); 349 350 test(function () { 351 var element = define_new_custom_element([]); 352 var instance = document.createElement(element.name); 353 instance.setAttribute('data-lang', 'ja'); 354 assert_array_equals(element.takeLog().types(), ['constructed']); 355 testFunction(instance, 'data-lang'); 356 assert_array_equals(element.takeLog().types(), []); 357 }, name + ' must not enqueue an attributeChanged reaction when removing an existing unobserved attribute'); 358 } 359 360 function test_mutating_style_property_value(testFunction, name, options) { 361 const propertyName = (options || {}).propertyName || 'color'; 362 const idlName = (options || {}).idlName || 'color'; 363 const value1 = (options || {}).value1 || 'blue'; 364 const rule1 = `${propertyName}: ${value1};`; 365 const value2 = (options || {}).value2 || 'red'; 366 const rule2 = `${propertyName}: ${value2};`; 367 368 test(function () { 369 var element = define_new_custom_element(['style']); 370 var instance = document.createElement(element.name); 371 assert_array_equals(element.takeLog().types(), ['constructed']); 372 testFunction(instance, propertyName, idlName, value1); 373 assert_equals(instance.getAttribute('style'), rule1); 374 var logEntries = element.takeLog(); 375 assert_array_equals(logEntries.types(), ['attributeChanged']); 376 assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: null, newValue: rule1, namespace: null}); 377 }, name + ' must enqueue an attributeChanged reaction when it adds the observed style attribute'); 378 379 test(function () { 380 var element = define_new_custom_element(['title']); 381 var instance = document.createElement(element.name); 382 assert_array_equals(element.takeLog().types(), ['constructed']); 383 testFunction(instance, propertyName, idlName, value1); 384 assert_equals(instance.getAttribute('style'), rule1); 385 assert_array_equals(element.takeLog().types(), []); 386 }, name + ' must not enqueue an attributeChanged reaction when it adds the style attribute but the style attribute is not observed'); 387 388 test(function () { 389 var element = define_new_custom_element(['style']); 390 var instance = document.createElement(element.name); 391 testFunction(instance, propertyName, idlName, value1); 392 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 393 testFunction(instance, propertyName, idlName, value2); 394 assert_equals(instance.getAttribute('style'), rule2); 395 var logEntries = element.takeLog(); 396 assert_array_equals(logEntries.types(), ['attributeChanged']); 397 assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: rule1, newValue: rule2, namespace: null}); 398 }, name + ' must enqueue an attributeChanged reaction when it mutates the observed style attribute'); 399 400 test(function () { 401 var element = define_new_custom_element([]); 402 var instance = document.createElement(element.name); 403 testFunction(instance, propertyName, idlName, value1); 404 assert_array_equals(element.takeLog().types(), ['constructed']); 405 testFunction(instance, propertyName, idlName, value2); 406 assert_equals(instance.getAttribute('style'), rule2); 407 assert_array_equals(element.takeLog().types(), []); 408 }, name + ' must not enqueue an attributeChanged reaction when it mutates the style attribute but the style attribute is not observed'); 409 } 410 411 function test_removing_style_property_value(testFunction, name) { 412 test(function () { 413 var element = define_new_custom_element(['style']); 414 var instance = document.createElement(element.name); 415 instance.setAttribute('style', 'color: red; display: none;'); 416 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 417 testFunction(instance, 'color', 'color'); 418 assert_equals(instance.getAttribute('style'), 'display: none;'); // Don't make this empty since browser behaviors are inconsistent now. 419 var logEntries = element.takeLog(); 420 assert_array_equals(logEntries.types(), ['attributeChanged']); 421 assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: 'color: red; display: none;', newValue: 'display: none;', namespace: null}); 422 }, name + ' must enqueue an attributeChanged reaction when it removes a property from the observed style attribute'); 423 424 test(function () { 425 var element = define_new_custom_element(['class']); 426 var instance = document.createElement(element.name); 427 instance.setAttribute('style', 'color: red; display: none;'); 428 assert_array_equals(element.takeLog().types(), ['constructed']); 429 testFunction(instance, 'color', 'color'); 430 assert_equals(instance.getAttribute('style'), 'display: none;'); // Don't make this empty since browser behaviors are inconsistent now. 431 assert_array_equals(element.takeLog().types(), []); 432 }, name + ' must not enqueue an attributeChanged reaction when it removes a property from the style attribute but the style attribute is not observed'); 433 } 434 435 function test_mutating_style_property_priority(testFunction, name) { 436 test(function () { 437 var element = define_new_custom_element(['style']); 438 var instance = document.createElement(element.name); 439 instance.setAttribute('style', 'color: red'); 440 assert_array_equals(element.takeLog().types(), ['constructed', 'attributeChanged']); 441 testFunction(instance, 'color', 'color', true); 442 assert_equals(instance.getAttribute('style'), 'color: red !important;'); 443 var logEntries = element.takeLog(); 444 assert_array_equals(logEntries.types(), ['attributeChanged']); 445 assert_attribute_log_entry(logEntries.last(), {name: 'style', oldValue: 'color: red', newValue: 'color: red !important;', namespace: null}); 446 }, name + ' must enqueue an attributeChanged reaction when it makes a property important and the style attribute is observed'); 447 448 test(function () { 449 var element = define_new_custom_element(['id']); 450 var instance = document.createElement(element.name); 451 instance.setAttribute('style', 'color: red'); 452 assert_array_equals(element.takeLog().types(), ['constructed']); 453 testFunction(instance, 'color', 'color', true); 454 assert_equals(instance.getAttribute('style'), 'color: red !important;'); 455 assert_array_equals(element.takeLog().types(), []); 456 }, name + ' must enqueue an attributeChanged reaction when it makes a property important but the style attribute is not observed'); 457 }