slotchange-events.html (5264B)
1 <!DOCTYPE html> 2 <title>slotchanged event</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <body> 6 7 <script> 8 customElements.define( 9 "custom-element", 10 class extends HTMLElement { 11 constructor() { 12 super(); 13 const shadowRoot = this.attachShadow({mode: "open"}); 14 const slot = document.createElement('slot'); 15 slot.name = 'content'; 16 shadowRoot.append(slot); 17 } 18 }, 19 ); 20 21 promise_test(async t => { 22 const customElement = document.body.appendChild(document.createElement('custom-element')); 23 t.add_cleanup(() => customElement.remove()); 24 25 const slot = customElement.shadowRoot.children[0]; 26 const slotChangePromise = new Promise((resolve, reject) => { 27 slot.addEventListener('slotchange', e => resolve(), {once: true}); 28 t.step_timeout(() => reject('Timeout; slotchange was not fired'), 1500); 29 }); 30 31 const defaultContentP = customElement.shadowRoot.appendChild(document.createElement('p')); 32 slot.moveBefore(defaultContentP, null); 33 await slotChangePromise; 34 }, "Moving default content into a slot fires 'slotchange' event"); 35 36 promise_test(async t => { 37 const customElement = document.body.appendChild(document.createElement('custom-element')); 38 t.add_cleanup(() => customElement.remove()); 39 40 const slot = customElement.shadowRoot.children[0]; 41 const defaultContentP = slot.appendChild(document.createElement('p')); 42 43 // Wait for "signal a slot change" to asynchronously settle. This should fire 44 // the 'slotchange' event for the insertion above, but we will not assert that, 45 // since this test is only testing that 'slotchange' is fired on removal. We 46 // separate this out in case an implementation fires one but not the other 47 // (i.e., Chromium, at the time of writing this). 48 await new Promise(resolve => t.step_timeout(() => resolve())); 49 50 const slotChangePromise = new Promise((resolve, reject) => { 51 slot.addEventListener('slotchange', e => resolve(), {once: true}); 52 t.step_timeout(() => reject('Timeout; slotchange was not fired'), 1500); 53 }); 54 55 // Move `defaultContentP` OUT of the slot, and into the ShadowRoot. This 56 // triggers "signal a slot change" on `defaultContentP`'s old parent, which is 57 // the slot. 58 customElement.shadowRoot.moveBefore(defaultContentP, null); 59 await slotChangePromise; 60 }, "Moving default content out of a slot fires 'slotchange' event"); 61 62 promise_test(async t => { 63 const customElement = document.body.appendChild(document.createElement('custom-element')); 64 t.add_cleanup(() => customElement.remove()); 65 66 const slot = customElement.shadowRoot.children[0]; 67 const slottable = document.body.appendChild(document.createElement('p')); 68 slottable.slot = 'content'; 69 70 { 71 const slotChangePromise = new Promise((resolve, reject) => { 72 slot.addEventListener('slotchange', e => { 73 if (slot.assignedNodes().includes(slottable)) { 74 resolve(); 75 } else { 76 reject('slot.assignedNodes() did not include the slottable after move'); 77 } 78 }, {once: true}); 79 80 t.step_timeout(() => reject('Timeout; slotchange (whiling moving an element in) was not fired'), 1500); 81 }); 82 83 // Move the slottable INTO the custom element, thus slotting it. 84 customElement.moveBefore(slottable, null); 85 await slotChangePromise; 86 } 87 88 { 89 const slotChangePromise = new Promise((resolve, reject) => { 90 slot.addEventListener('slotchange', e => { 91 if (slot.assignedNodes().length === 0) { 92 resolve(); 93 } else { 94 reject('slot.assignedNodes() not empty after the slottable moved out'); 95 } 96 }, {once: true}); 97 98 t.step_timeout(() => reject('Timeout; slotchange (whiling moving an element out) was not fired'), 1500); 99 }); 100 101 // Move the slottable OUT of the custom element, thus unslotting it. 102 document.body.moveBefore(slottable, null); 103 await slotChangePromise; 104 } 105 }, "Moving a slottable into and out out of a custom element fires 'slotchange' event"); 106 107 promise_test(async t => { 108 const customElement = document.body.appendChild(document.createElement('custom-element')); 109 const slot = customElement.shadowRoot.children[0]; 110 t.add_cleanup(() => customElement.remove()); 111 112 const p = document.createElement('p'); 113 p.slot = 'content'; 114 p.textContent = 'The content'; 115 customElement.appendChild(p); 116 117 // See the above tests that do the same thing, for implementations that do not fire `slotchange` 118 // at this phase. 119 await new Promise(resolve => t.step_timeout(() => resolve())); 120 121 assert_array_equals(slot.assignedNodes(), [p]); 122 document.body.moveBefore(slot, null); 123 124 await new Promise((resolve, reject) => { 125 slot.addEventListener('slotchange', e => resolve(), {once: true}); 126 t.step_timeout(() => reject('Timeout; slotchange was not fired2'), 1500); 127 }); 128 129 assert_array_equals(slot.assignedNodes(), []); 130 customElement.shadowRoot.moveBefore(slot, null); 131 132 await new Promise((resolve, reject) => { 133 slot.addEventListener('slotchange', e => resolve(), {once: true}); 134 t.step_timeout(() => reject('Timeout; slotchange was not fired3'), 1500); 135 }); 136 137 assert_array_equals(slot.assignedNodes(), [p]); 138 }, "Moving a slot runs the assign slottables algorithm"); 139 </script>