Add children and replaceChildren support to ChildNodePart

This also changes IsValid for ChildNodePart to return false if the
endpoints are the same. It seems like there needs to be a "gap"
between them or things get weird when you do replaceChildren.

Bug: 1453291
Change-Id: I0b7b0a1d7e47a3e315d20cfa09d0e55c2c06f346
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4681916
Commit-Queue: Mason Freed <masonf@chromium.org>
Reviewed-by: David Baron <dbaron@chromium.org>
Auto-Submit: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1169613}
diff --git a/dom/parts/basic-dom-part-objects.tentative.html b/dom/parts/basic-dom-part-objects.tentative.html
index 7b051f2..db41c9e 100644
--- a/dom/parts/basic-dom-part-objects.tentative.html
+++ b/dom/parts/basic-dom-part-objects.tentative.html
@@ -7,12 +7,10 @@
 <template id=template>
   <div id=target style="display:none">
     Imperative test element
-    <span id=a>A</span>
-    <span id=b>B
-      <span id=sub>B-sub</span>
-    </span>
-    <span id=c>C</span>
-  </div>
+    <span id=a>A</span><span id=b>B
+      <span id=sub>B-sub1</span>
+      <span id=sub>B-sub2</span>
+    </span><span id=c>C</span></div>
 </template>
 
 <div style="display:none">
@@ -107,7 +105,7 @@
     const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2]));
     const nodePart3 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['this is','part 3']}));
     const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['this','is part 2']}));
-    const childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild,{metadata: ['childnodepart2']}));
+    const childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild.nextSibling,{metadata: ['childnodepart2']}));
     assert_array_equals(root.getParts(),[nodePart,childNodePart]);
     assert_array_equals(childNodePart.getParts(),[childNodePart2,nodePart3,nodePart2],'Parts on the same Node are returned in the order they were constructed');
     assert_array_equals(childNodePart2.getParts(),[]);
@@ -225,4 +223,31 @@
   document.body.appendChild(target); // Restore
   assert_array_equals(root.getParts(),[childPartAC]);
 }, 'DOM mutation support');
+
+
+test((t) => {
+  const root = document.getPartRoot();
+  assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
+  const target = document.querySelector('#target');
+  const a = document.querySelector('#a');
+  const b = document.querySelector('#b');
+  const c = document.querySelector('#c');
+  const otherNode = document.createElement('div');
+
+  const childPartAA = addCleanup(t,new ChildNodePart(root,a,a));
+  const childPartAB = addCleanup(t,new ChildNodePart(root,a,b));
+  const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
+  assert_throws_dom('InvalidStateError',() => childPartAA.replaceChildren(otherNode),'Can\'t replace children if part is invalid');
+  assert_array_equals(childPartAA.children,[],'Invalid parts should return empty children');
+  assert_array_equals(childPartAB.children,[],'Children should not include endpoints');
+  assert_array_equals(childPartAC.children,[b],'Children should not include endpoints');
+  childPartAB.replaceChildren(otherNode);
+  assert_array_equals(childPartAB.children,[otherNode],'Replacechildren should work');
+  assert_array_equals(childPartAC.children,[otherNode,b],'replaceChildren should leave endpoints alone');
+  childPartAC.replaceChildren(otherNode);
+  assert_array_equals(childPartAC.children,[otherNode],'Replacechildren with existing children should work');
+  assert_array_equals(childPartAB.children,[]);
+  childPartAC.replaceChildren(b);
+  assert_array_equals(target.children,[a,b,c]);
+}, 'ChildNodePart children manipulation');
 </script>