Bug 1633854 [wpt PR 23304] - Adds slotchange event when inserting and removing slotable for manual slot assignment., a=testonly
authorYu Han <yuzhehan@chromium.org>
Wed, 13 May 2020 09:38:52 +0000
changeset 531049 99c05982e3897112b1def1695e0bb27aa40dc44d
parent 531048 50c1856fd3fa181f850f905bf273123ca03d2157
child 531050 ab975ede8506e509b8fe638bd25efea774441885
push id37435
push userapavel@mozilla.com
push dateWed, 20 May 2020 15:28:23 +0000
treeherdermozilla-central@5415da14ec9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1633854, 23304, 869308, 2169577, 765096
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1633854 [wpt PR 23304] - Adds slotchange event when inserting and removing slotable for manual slot assignment., a=testonly Automatic update from web-platform-tests Adds slotchange event when inserting and removing slotable for manual slot assignment. This CL adds tests to ensure slotchange event is fired when assigned nodes or slots are removed. They also test that slotchange event isn't raised when slots and slotables are inserted within a shadow host w/ manual slot assignment shadow root. In addition to tests, this CL adds the ability to raised slotchange event when a slot containing assigned nodes is removed from the shadow root. Most of the other test cases have already been implemented because node insertion and removal run assign slottables algorithm. Bug: 869308 Change-Id: I80212af34e569f9b80261c24253f38fe91d05a68 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2169577 Commit-Queue: Yu Han <yuzhehan@chromium.org> Reviewed-by: Mason Freed <masonfreed@chromium.org> Cr-Commit-Position: refs/heads/master@{#765096} -- wpt-commits: 1114960c812943c3c92b1a52735ed574b0f19558 wpt-pr: 23304
testing/web-platform/tests/shadow-dom/slots-imperative-api-slotchange.tentative.html
--- a/testing/web-platform/tests/shadow-dom/slots-imperative-api-slotchange.tentative.html
+++ b/testing/web-platform/tests/shadow-dom/slots-imperative-api-slotchange.tentative.html
@@ -4,37 +4,40 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <link rel="help" href="https://dom.spec.whatwg.org/#signaling-slot-change">
 <script src="resources/shadow-dom.js"></script>
 
 <div id="test_slotchange">
   <div id="host">
     <template id="shadow_root" data-mode="open" data-slot-assignment="manual">
-      <slot id="s1"></slot>
+      <slot id="s1"><div id="fb">fallback</div></slot>
       <slot id="s2"></slot>
+      <slot id="s3"></slot>
     </template>
     <div id="c1"></div>
     <div id="c2"></div>
   </div>
   <div id="c4"></div>
 </div>
 
 <script>
 function getDataCollection() {
   return {
     s1EventCount: 0,
     s2EventCount: 0,
+    s3EventCount: 0,
     s1ResolveFn: null,
-    s2ResolveFn: null
+    s2ResolveFn: null,
+    s3ResolveFn: null,
   }
 }
 
-function setupShadowDOM(test, data) {
-  let tTree = createTestTree(test_slotchange);
+function setupShadowDOM(id, test, data) {
+  let tTree = createTestTree(id);
   tTree.s1.addEventListener('slotchange', (event) => {
     if (!event.isFakeEvent) {
       test.step(function () {
           assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
           assert_equals(event.target, tTree.s1, 'slotchange event\'s target must be the slot element');
           assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
       });
       data.s1EventCount++;
@@ -47,52 +50,66 @@ function setupShadowDOM(test, data) {
           assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
           assert_equals(event.target, tTree.s2, 'slotchange event\'s target must be the slot element');
           assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
       });
       data.s2EventCount++;
     }
     data.s2ResolveFn();
   });
+  tTree.s3.addEventListener('slotchange', (event) => {
+    if (!event.isFakeEvent) {
+      test.step(function () {
+        assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
+        // listen to bubbling events.
+        assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
+      });
+      data.s3EventCount++;
+    }
+    data.s3ResolveFn();
+  });
   return tTree;
 }
 
 function monitorSlots(data) {
     const s1Promise = new Promise((resolve, reject) => {
       data.s1ResolveFn = resolve;
     });
     const s2Promise = new Promise((resolve, reject) => {
       data.s2ResolveFn = resolve;
     });
-    return [s1Promise, s2Promise];
+    const s3Promise = new Promise((resolve, reject) => {
+      data.s3ResolveFn = resolve;
+    });
+    return [s1Promise, s2Promise, s3Promise];
 }
 </script>
 
 <script>
 // Tests:
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise, s2Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1]);
   tTree.s2.assign([tTree.c2]);
 
   assert_equals(data.s1EventCount, 0, 'slotchange event must not be fired synchronously');
   assert_equals(data.s2EventCount, 0);
 
   Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
     assert_equals(data.s1EventCount, 1);
     assert_equals(data.s2EventCount, 1);
   }));
 }, 'slotchange event must not fire synchronously.');
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise, s2Promise] = monitorSlots(data);
 
   assert_throws_dom('NotAllowedError', () => { tTree.s1.assign([tTree.c4]); });
   tTree.s2.assign([]);
   tTree.host.insertBefore(tTree.c4, tTree.c1);
 
   Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
     assert_equals(data.s1EventCount, 0);
@@ -103,17 +120,17 @@ async_test((test) => {
   let fakeEvent = new Event('slotchange');
   fakeEvent.isFakeEvent = true;
   tTree.s1.dispatchEvent(fakeEvent);
   tTree.s2.dispatchEvent(fakeEvent);
 }, 'slotchange event should not fire when assignments do not change assignedNodes.');
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange,test, data);
   let [s1Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1, tTree.c2]);
 
   s1Promise.then(test.step_func(() => {
     assert_equals(data.s1EventCount, 1);
 
     [s1Promise] = monitorSlots(data);
@@ -128,31 +145,31 @@ async_test((test) => {
     fakeEvent.isFakeEvent = true;
     tTree.s1.dispatchEvent(fakeEvent);
   }));
 
 }, 'slotchange event should not fire when same node is assigned.');
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise, s2Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1]);
   tTree.s2.assign([tTree.c2]);
 
   Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
     assert_equals(data.s1EventCount, 1);
     assert_equals(data.s2EventCount, 1);
   }));
 }, "Fire slotchange event when slot's assigned nodes changes.");
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise, s2Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1]);
 
   s1Promise.then(test.step_func(() => {
     assert_equals(data.s1EventCount, 1);
 
     [s1Promise, s2Promise] = monitorSlots(data);
@@ -162,17 +179,17 @@ async_test((test) => {
       assert_equals(data.s1EventCount, 2);
       assert_equals(data.s2EventCount, 1);
     }));
   }));
 }, "Fire slotchange event on previous slot and new slot when node is reassigned.");
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1]);
 
   s1Promise.then(test.step_func(() => {
     assert_equals(data.s1EventCount, 1);
 
     [s1Promise] = monitorSlots(data);
@@ -181,25 +198,176 @@ async_test((test) => {
     s1Promise.then(test.step_func_done(() => {
       assert_equals(data.s1EventCount, 2);
     }));
   }));
 }, "Fire slotchange event on node assignment and when assigned node is removed.");
 
 async_test((test) => {
   const data = getDataCollection();
-  let tTree = setupShadowDOM(test, data);
+  let tTree = setupShadowDOM(test_slotchange, test, data);
   let [s1Promise] = monitorSlots(data);
 
   tTree.s1.assign([tTree.c1, tTree.c2]);
 
   s1Promise.then(test.step_func(() => {
     assert_equals(data.s1EventCount, 1);
 
     [s1Promise] = monitorSlots(data);
     tTree.s1.assign([tTree.c2, tTree.c1]);
 
     s1Promise.then(test.step_func_done(() => {
       assert_equals(data.s1EventCount, 2);
     }));
   }));
 }, "Fire slotchange event when order of assigned nodes changes.");
+
+promise_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_slotchange, test, data);
+  let [s1Promise] = monitorSlots(data);
+
+  assert_array_equals(tTree.s1.assignedNodes({ flatten: true }), [tTree.fb]);
+  let div = document.createElement("div");
+  tTree.s1.appendChild(div);
+
+  return s1Promise.then(test.step_func(() => {
+    assert_equals(data.s1EventCount, 1);
+    assert_array_equals(tTree.s1.assignedNodes({ flatten: true }), [tTree.fb, div]);
+  }));
+}, "Fire slotchange event when slot's fallback content changes.");
+
+promise_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_slotchange, test, data);
+  let [s1Promise] = monitorSlots(data);
+
+  tTree.s1.assign([tTree.c1]);
+
+  return s1Promise.then(test.step_func(() => {
+    assert_equals(data.s1EventCount, 1);
+
+    [s1Promise] = monitorSlots(data);
+    tTree.c1.remove();
+
+    return s1Promise;
+  }))
+  .then(test.step_func(() => {
+      assert_equals(data.s1EventCount, 2);
+  }));
+}, "Fire slotchange event when assigned node is removed.");
+
+promise_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_slotchange, test, data);
+  [s1Promise] = monitorSlots(data);
+
+  tTree.s1.assign([tTree.c1]);
+
+  return s1Promise.then(test.step_func(() => {
+    assert_equals(data.s1EventCount, 1);
+
+    [s1Promise] = monitorSlots(data);
+    tTree.s1.remove();
+
+    return s1Promise;
+  }))
+  .then(test.step_func(() => {
+      assert_equals(data.s1EventCount, 2);
+  }));
+}, "Fire slotchange event when removing a slot from Shadows Root that changes its assigned nodes.");
+
+async_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_slotchange, test, data);
+  let [s1Promise] = monitorSlots(data);
+
+  tTree.s1.remove();
+
+  let fakeEvent = new Event('slotchange');
+  fakeEvent.isFakeEvent = true;
+  tTree.s1.dispatchEvent(fakeEvent);
+
+  s1Promise.then(test.step_func(() => {
+    assert_equals(data.s2EventCount, 0);
+
+    [s1Promise, s2Promise] = monitorSlots(data);
+    tTree.shadow_root.insertBefore(tTree.s1, tTree.s2);
+
+    tTree.s1.dispatchEvent(fakeEvent);
+    tTree.s2.dispatchEvent(fakeEvent);
+
+    Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
+      assert_equals(data.s1EventCount, 0);
+      assert_equals(data.s2EventCount, 0);
+    }));
+  }));
+
+}, "No slotchange event when adding or removing an empty slot.");
+
+async_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_slotchange, test, data);
+  let [s1Promise, s2Promise] = monitorSlots(data);
+
+  tTree.host.appendChild(document.createElement("div"));
+
+  let fakeEvent = new Event('slotchange');
+  fakeEvent.isFakeEvent = true;
+  tTree.s1.dispatchEvent(fakeEvent);
+  tTree.s2.dispatchEvent(fakeEvent);
+
+  Promise.all([s1Promise, s2Promise]).then(test.step_func(() => {
+    assert_equals(data.s1EventCount, 0);
+    assert_equals(data.s2EventCount, 0);
+
+    [s1Promise, s2Promise] = monitorSlots(data);
+    tTree.shadow_root.insertBefore(document.createElement("div"), tTree.s2);
+
+    tTree.s1.dispatchEvent(fakeEvent);
+    tTree.s2.dispatchEvent(fakeEvent);
+
+    Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
+      assert_equals(data.s1EventCount, 0);
+      assert_equals(data.s2EventCount, 0);
+    }));
+  }));
+
+}, "No slotchange event when adding another slotable.");
+
 </script>
+
+<div id="test_nested_slotchange">
+  <div>
+    <template data-mode="open" data-slot-assignment="manual">
+      <div>
+        <template data-mode="open" data-slot-assignment="manual">
+          <slot id="s2"></slot>
+          <slot id="s3"></slot>
+        </template>
+        <slot id="s1"></slot>
+      </div>
+    </template>
+    <div id="c1"></div>
+  </div>
+</div>
+
+<script>
+async_test((test) => {
+  const data = getDataCollection();
+  let tTree = setupShadowDOM(test_nested_slotchange, test, data);
+  let [s1Promise, s2Promise, s3Promise] = monitorSlots(data);
+
+  tTree.s3.assign([tTree.s1]);
+
+  s3Promise.then(test.step_func(() => {
+    assert_equals(data.s3EventCount, 1);
+    [s1Promise, s2Promise, s3Promise] = monitorSlots(data);
+
+    tTree.s1.assign([tTree.c1]);
+
+    Promise.all([s1Promise, s3Promise]).then(test.step_func_done(() => {
+      assert_equals(data.s1EventCount, 1);
+      assert_equals(data.s3EventCount, 2);
+    }));
+  }));
+}, "Fire slotchange event when assign node to nested slot, ensure event bubbles ups.");
+</script>