Add declarative DOM Parts syntax
This CL adds the ability to construct DOM Parts during document
parsing via "fake" processing instructions:
<section>
<h1 id="name">
<?child-node-part?> <?/child-node-part?>
</h1>
Email: <?node-part metadata?><a id="link"></a>
</section>
In this example, <?child-node-part?><?/child-node-part?> will
be left in the document, but a ChildNodePart will also be constructed
with previous/next endpoints of those two comment nodes. And the
<?node-part> will be left in place, but a NodePart will be attached
to the <a> element.
Note that <?foo?> is exactly equivalent to <!--?foo?-->, and both
are tested in the new WPT.
This CL also:
- adds a virtual test suite that makes sure no functionality breaks
or leaks when DOMPartAPI is disabled.
- revamps the WPT, adding a `assertEqualParts()` that checks a given
parts list via the type of parts and the metadata. This is then
used everywhere.
- add testing of declarative DOM Parts for three cases:
1. main document parsing (passes with this CL)
2. template parsing (doesn't yet pass)
3. cloned templates (not even sure if this should ever pass)
Bug: 1453291,1465062
Change-Id: I2eaef72a21fc2deaa251deb0731b5ff55ef19d8c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4686189
Reviewed-by: David Baron <dbaron@chromium.org>
Auto-Submit: Mason Freed <masonf@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
Reviewed-by: Stefan Zager <szager@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1170849}
diff --git a/dom/parts/basic-dom-part-objects.tentative.html b/dom/parts/basic-dom-part-objects.tentative.html
index db41c9e..a60ec8b 100644
--- a/dom/parts/basic-dom-part-objects.tentative.html
+++ b/dom/parts/basic-dom-part-objects.tentative.html
@@ -4,34 +4,54 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
-<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-sub1</span>
- <span id=sub>B-sub2</span>
- </span><span id=c>C</span></div>
-</template>
-
-<div style="display:none">
- Declarative syntax
- <h1 id="name">First<?child-node-part name?>Middle<?/child-node-part?>Last</h1>
- Email: <?node-part email-link?><a id="link"></a>
-</div>
+<body>
<script>
-const template = document.getElementById('template');
-document.body.appendChild(template.content.cloneNode(true));
+ function assertEqualParts(parts,partDescriptions,expectedParts,description) {
+ assert_equals(parts.length,partDescriptions.length,`${description}: lengths differ`);
+ for(let i=0;i<parts.length;++i) {
+ assert_true(parts[i] instanceof Part,`${description}: not a Part`);
+ assert_true(parts[i] instanceof window[partDescriptions[i].type],`${description}: index ${i} expected ${partDescriptions[i].type}`);
+ assert_array_equals(parts[i].metadata,partDescriptions[i].metadata,`${description}: index ${i} wrong metadata`);
+ if (expectedParts) {
+ assert_equals(parts[i],expectedParts[i],`${description}: index ${i} object equality`);
+ }
+ }
+ }
+</script>
+
+<template id=imperative>
+ <div>
+ <div id=target1 style="display:none">
+ Imperative test element
+ <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>
+ </div>
+</template>
+
+<script>{
+const template = document.getElementById('imperative');
function addCleanup(t, part) {
t.add_cleanup(() => part.disconnect());
return part;
}
-
[false,true].forEach(useTemplate => {
const doc = useTemplate ? template.content : document;
- const target = doc.querySelector('#target');
- assert_true(!!(doc && target && target.children.length >= 3));
+ let target,wrapper;
+ if (useTemplate) {
+ target = doc.querySelector('#target1');
+ } else {
+ wrapper = document.body.appendChild(document.createElement('div'));
+ wrapper.appendChild(template.content.cloneNode(true));
+ target = wrapper.querySelector('#target1');
+ }
+ const a = target.querySelector('#a');
+ const b = target.querySelector('#b');
+ const c = target.querySelector('#c');
+ assert_true(!!(doc && target && target.parentElement && a && b && c));
const description = useTemplate ? "DocumentFragment" : "Document";
test((t) => {
const root = doc.getPartRoot();
@@ -41,42 +61,39 @@
assert_true(root.rootContainer instanceof (useTemplate ? DocumentFragment : Document));
const nodePart = addCleanup(t,new NodePart(root,target,{metadata: ['foo']}));
- assert_true(nodePart instanceof NodePart);
+ assertEqualParts([nodePart],[{type:'NodePart',metadata:['foo']}],0,'Basic NodePart');
assert_equals(nodePart.node,target);
assert_equals(nodePart.root,root);
- assert_array_equals(nodePart.metadata,['foo']);
- assert_equals(root.getParts().length,1,'getParts() for the root should now have this nodePart');
- assert_equals(root.getParts()[0],nodePart);
+ let runningPartsExpectation = [{type:'NodePart',metadata:['foo']}];
+ assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart],'getParts() for the root should now have this nodePart');
assert_equals(parts.length,0,'Return value of getParts() is not live');
assert_throws_js(TypeError,() => new NodePart(nodePart,target.children[0]),'Constructing a Part with a NodePart as the PartRoot should throw');
const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['bar','baz']}));
- assert_true(childNodePart instanceof ChildNodePart);
- assert_true(childNodePart instanceof Part);
+ assertEqualParts([childNodePart],[{type:'ChildNodePart',metadata:['bar','baz']}],0,'Basic ChildNodePart');
assert_equals(childNodePart.root,root);
assert_equals(childNodePart.previousSibling,target.children[0]);
assert_equals(childNodePart.nextSibling,target.children[2]);
- assert_array_equals(childNodePart.metadata,['bar','baz']);
assert_equals(childNodePart.getParts().length,0,'childNodePart.getParts() should start out empty');
- assert_equals(root.getParts().length,2);
- assert_equals(root.getParts()[1],childNodePart);
+ runningPartsExpectation.push({type:'ChildNodePart',metadata:['bar','baz']});
+ assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,childNodePart],'getParts() for the root should now have this childNodePart');
const nodeBefore = target.previousSibling || target.parentNode;
const nodePartBefore = addCleanup(t,new NodePart(root,nodeBefore));
- assert_equals(root.getParts().length,3,'getParts() for the root should now have this nodePart');
- assert_array_equals(root.getParts(),[nodePartBefore,nodePart,childNodePart],'getParts() should return nodes in tree order');
+ runningPartsExpectation.unshift({type:'NodePart',metadata:[]});
+ assertEqualParts(root.getParts(),runningPartsExpectation,[nodePartBefore,nodePart,childNodePart],'getParts() for the root should now have this nodePart, in tree order');
- const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[2]));
+ const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[2],{metadata:['blah']}));
assert_equals(nodePart2.root,childNodePart);
- assert_equals(root.getParts().length,3,'getParts() for the root DocumentPartRoot shouldn\'t change');
- assert_array_equals(childNodePart.getParts(),[nodePart2]);
+ assertEqualParts(root.getParts(),runningPartsExpectation,[nodePartBefore,nodePart,childNodePart],'getParts() for the root DocumentPartRoot shouldn\'t change');
+ assertEqualParts(childNodePart.getParts(),[{type:'NodePart',metadata:['blah']}],[nodePart2],'getParts() for the childNodePart should have it');
nodePart2.disconnect();
assert_equals(nodePart2.root,null,'root should be null after disconnect');
assert_equals(nodePart2.node,null,'node should be null after disconnect');
assert_equals(childNodePart.getParts().length,0,'calling disconnect() should remove the part from root.getParts()');
- assert_equals(root.getParts().length,3,'getParts() for the root DocumentPartRoot still shouldn\'t change');
+ assertEqualParts(root.getParts(),runningPartsExpectation,[nodePartBefore,nodePart,childNodePart],'getParts() for the root DocumentPartRoot still shouldn\'t change');
nodePart2.disconnect(); // Calling twice should be ok.
childNodePart.disconnect();
@@ -101,13 +118,15 @@
test((t) => {
const root = doc.getPartRoot();
- const nodePart = addCleanup(t,new NodePart(root,target));
- 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 nodePart = addCleanup(t,new NodePart(root,target,{metadata:['node1']}));
+ const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['child']}));
+ const nodePart3 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 3']}));
+ const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 2']}));
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');
+ let rootExpectations = [{type:'NodePart',metadata:['node1']},{type:'ChildNodePart',metadata:['child']}];
+ assertEqualParts(root.getParts(),rootExpectations,[nodePart,childNodePart],'setup');
+ let childExpectations = [{type:'ChildNodePart',metadata:['childnodepart2']},{type:'NodePart',metadata:['node 3']},{type:'NodePart',metadata:['node 2']}];
+ assertEqualParts(childNodePart.getParts(),childExpectations,[childNodePart2,nodePart3,nodePart2],'setup');
assert_array_equals(childNodePart2.getParts(),[]);
// Test cloning of the entire DocumentPartRoot.
@@ -118,21 +137,18 @@
assert_not_equals(clonedPartRoot,root);
assert_not_equals(clonedContainer,doc);
assert_equals(doc.innerHTML,clonedContainer.innerHTML);
- assert_equals(clonedPartRoot.getParts().length,root.getParts().length);
- assert_array_equals(root.getParts(),[nodePart,childNodePart]);
+ assertEqualParts(clonedPartRoot.getParts(),rootExpectations,0,'cloned PartRoot should contain identical parts');
assert_true(!clonedPartRoot.getParts().includes(nodePart),'Original parts should not be retained');
assert_true(!clonedPartRoot.getParts().includes(childNodePart));
const newNodePart = clonedPartRoot.getParts()[0];
const newChildNodePart = clonedPartRoot.getParts()[1];
- assert_true(newChildNodePart instanceof ChildNodePart,'Cloned parts are out of order');
- assert_true(newNodePart instanceof NodePart);
assert_not_equals(newNodePart.node,target,'Node references should not point to original nodes');
- assert_equals(newNodePart.node.id,'target','New parts should point to cloned nodes');
+ assert_equals(newNodePart.node.id,target.id,'New parts should point to cloned nodes');
assert_not_equals(newChildNodePart.previousSibling,a,'Node references should not point to original nodes');
assert_equals(newChildNodePart.previousSibling.id,'a');
assert_not_equals(newChildNodePart.nextSibling,c,'Node references should not point to original nodes');
assert_equals(newChildNodePart.nextSibling.id,'c');
- assert_equals(newChildNodePart.getParts().length,childNodePart.getParts().length);
+ assertEqualParts(newChildNodePart.getParts(),childExpectations,0,'cloned PartRoot should contain identical parts');
// Test cloning of ChildNodeParts.
const clonedChildNodePartRoot = childNodePart.clone();
@@ -141,12 +157,7 @@
assert_true(clonedChildContainer instanceof Element);
assert_not_equals(clonedChildContainer,target);
assert_equals(clonedChildContainer.outerHTML,cloneRange(target,a,c).outerHTML);
- assert_equals(clonedChildNodePartRoot.getParts().length,3);
- const clonedSubParts = clonedChildNodePartRoot.getParts();
- assert_true(clonedSubParts[0] instanceof ChildNodePart);
- assert_array_equals(clonedSubParts[0].metadata,childNodePart2.metadata,'metadata should be preserved, and...');
- assert_array_equals(clonedSubParts[1].metadata,nodePart3.metadata,'parts should still be in construction order');
- assert_array_equals(clonedSubParts[2].metadata,nodePart2.metadata);
+ assertEqualParts(clonedChildNodePartRoot.getParts(),childExpectations,0,'clone of childNodePart should match');
}, `Cloning (${description})`);
['Element','Text','Comment','CDATASection','ProcessingInstruction'].forEach(nodeType => {
@@ -166,88 +177,180 @@
}
t.add_cleanup(() => node.remove());
doc.firstElementChild.append(node);
- const nodePart = addCleanup(t,new NodePart(root,node));
+ const nodePart = addCleanup(t,new NodePart(root,node,{metadata:['foobar']}));
assert_true(!!nodePart);
const clone = root.clone();
assert_equals(clone.getParts().length,1);
- const clonedPart = clone.getParts()[0];
- assert_true(clonedPart instanceof NodePart);
- assert_true(clonedPart.node instanceof window[nodeType]);
+ assertEqualParts(clone.getParts(),[{type:'NodePart',metadata:['foobar']}],0,'getParts');
+ assert_true(clone.getParts()[0].node instanceof window[nodeType]);
}, `Cloning ${nodeType} (${description})`);
});
+
+ test((t) => {
+ const root = doc.getPartRoot();
+ assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
+
+ const nodePartB = addCleanup(t,new NodePart(root,b));
+ const nodePartA = addCleanup(t,new NodePart(root,a));
+ const nodePartC = addCleanup(t,new NodePart(root,c));
+ assert_array_equals(root.getParts(),[nodePartA,nodePartB,nodePartC]);
+ b.remove();
+ assert_array_equals(root.getParts(),[nodePartA,nodePartC]);
+ target.parentElement.insertBefore(b,target);
+ assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC]);
+ target.insertBefore(b,c);
+ assert_array_equals(root.getParts(),[nodePartA,nodePartB,nodePartC]);
+ nodePartA.disconnect();
+ nodePartB.disconnect();
+ nodePartC.disconnect();
+ assert_array_equals(root.getParts(),[]);
+
+ const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
+ assert_array_equals(root.getParts(),[childPartAC]);
+ a.remove();
+ assert_array_equals(root.getParts(),[],'Removing endpoints invalidates the part');
+ target.insertBefore(a,b); // Restore
+ assert_array_equals(root.getParts(),[childPartAC]);
+
+ target.insertBefore(c,a);
+ assert_array_equals(root.getParts(),[],'Endpoints out of order');
+ target.appendChild(c); // Restore
+ assert_array_equals(root.getParts(),[childPartAC]);
+
+ document.body.appendChild(c);
+ assert_array_equals(root.getParts(),[],'Children need to have same parent');
+ target.appendChild(c); // Restore
+ assert_array_equals(root.getParts(),[childPartAC]);
+
+ const oldParent = target.parentElement;
+ target.remove();
+ assert_array_equals(root.getParts(),[],'Parent needs to be connected');
+ oldParent.appendChild(target); // Restore
+ assert_array_equals(root.getParts(),[childPartAC]);
+ }, `DOM mutation support (${description})`);
+
+ test((t) => {
+ const root = doc.getPartRoot();
+ assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
+ 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 (${description})`);
+
+ wrapper?.remove(); // Cleanup
+});
+}</script>
+
+<div>
+ <!-- Note - the test will remove this chunk of DOM once the test completes -->
+ <div id=target2>
+ Declarative syntax - The template below (with id=declarative) should have
+ IDENTICAL STRUCTURE. There are three cases to test:
+ 1. Main document parsing (this chunk)
+ 2. Template parsing (the template below)
+ 3. Template/fragment cloning (the clone of the template below)
+ <h1 id="name">
+ <?child-node-part fullname?>
+ First
+ <!--?child-node-part middle?--> <?node-part middle-node?>Middle <?/child-node-part middle?>
+ Last
+ <!-- ?/child-node-part foobar? -->
+ </h1>
+ Email: <?node-part email-link?><a id="link"></a>
+
+ Here are some invalid parts that should not get parsed:
+ <!--child-node-part test comment without leading ?-->
+ <child-node-part test PI without leading ?>
+ <!--?child-node-partfoobar?-->
+ <?child-node-partfoobar?>
+</div>
+</div>
+<template id=declarative>
+ <div>
+ <div id=target3>Declarative syntax
+ <h1 id="name">
+ <?child-node-part fullname?>
+ First
+ <!--?child-node-part middle?--> <?node-part middle-node?>Middle <?/child-node-part middle?>
+ Last
+ <!-- ?/child-node-part foobar? -->
+ </h1>
+ Email: <?node-part email-link?><a id="link"></a>
+
+ Here are some invalid parts that should not get parsed:
+ <!--child-node-part test comment without leading ?-->
+ <child-node-part test PI without leading ?>
+ <!--?child-node-partfoobar?-->
+ <?child-node-partfoobar?>
+</div>
+ </div>
+</template>
+
+<script> {
+const template = document.getElementById('declarative');
+['Main Document','Template','Clone'].forEach(testCase => {
+ let doc,target,cleanup;
+ switch (testCase) {
+ case 'Main Document':
+ doc = document;
+ target = doc.querySelector('#target2');
+ cleanup = [target.parentElement];
+ break;
+ case 'Template':
+ doc = template.content;
+ target = doc.querySelector('#target3');
+ cleanup = [];
+ break;
+ case 'Clone':
+ doc = document;
+ wrapper = document.body.appendChild(document.createElement('div'));
+ wrapper.appendChild(template.content.cloneNode(true));
+ target = wrapper.querySelector('#target3');
+ cleanup = [wrapper];
+ break;
+ }
+ assert_true(!!(doc && target && target.parentElement));
+
+ function assertIsComment(node,commentText) {
+ assert_true(node instanceof Comment);
+ assert_equals(node.textContent,commentText);
+ }
+
+ test((t) => {
+ const root = doc.getPartRoot();
+ assert_true(root instanceof DocumentPartRoot);
+ const expectedRootParts = [{type:'ChildNodePart',metadata:['fullname']},{type:'NodePart',metadata:['email-link']}];
+ assertEqualParts(root.getParts(),expectedRootParts,0,'declarative root should have two parts');
+ assert_equals(root.getParts()[1].node,target.querySelector('#link'));
+ const childPart1 = root.getParts()[0];
+ assertIsComment(childPart1.previousSibling,'?child-node-part fullname?');
+ assertIsComment(childPart1.nextSibling,' ?/child-node-part foobar? ');
+ const expectedChild1Parts = [{type:'ChildNodePart',metadata:['middle']}];
+ assertEqualParts(childPart1.getParts(),expectedChild1Parts,0,'First level childpart should just have one child part');
+ const childPart2 = childPart1.getParts()[0];
+ assertIsComment(childPart2.previousSibling,'?child-node-part middle?');
+ assertIsComment(childPart2.nextSibling,'?/child-node-part middle?');
+ const expectedChild2Parts = [{type:'NodePart',metadata:['middle-node']}];
+ assertEqualParts(childPart2.getParts(),expectedChild2Parts,0,'Second level childpart should have just the node part');
+ assert_true(childPart2.getParts()[0].node instanceof Text);
+ assert_equals(childPart2.getParts()[0].node.textContent,'Middle ');
+ }, `Basic declarative DOM Parts (${testCase})`);
+
+ cleanup.forEach(el => el.remove()); // Cleanup
});
-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');
- assert_true(!!(target && a && b && c));
-
- const nodePartB = addCleanup(t,new NodePart(root,b));
- const nodePartA = addCleanup(t,new NodePart(root,a));
- const nodePartC = addCleanup(t,new NodePart(root,c));
- assert_array_equals(root.getParts(),[nodePartA,nodePartB,nodePartC]);
- b.remove();
- assert_array_equals(root.getParts(),[nodePartA,nodePartC]);
- document.body.appendChild(b);
- assert_array_equals(root.getParts(),[nodePartA,nodePartC,nodePartB]);
- target.insertBefore(b,a);
- assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC]);
- nodePartA.disconnect();
- nodePartB.disconnect();
- nodePartC.disconnect();
- assert_array_equals(root.getParts(),[]);
-
- const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
- assert_array_equals(root.getParts(),[childPartAC]);
- a.remove();
- assert_array_equals(root.getParts(),[],'Removing endpoints invalidates the part');
- target.insertBefore(a,b); // Restore
- assert_array_equals(root.getParts(),[childPartAC]);
-
- target.insertBefore(c,a);
- assert_array_equals(root.getParts(),[],'Endpoints out of order');
- target.appendChild(c); // Restore
- assert_array_equals(root.getParts(),[childPartAC]);
-
- document.body.appendChild(c);
- assert_array_equals(root.getParts(),[],'Children need to have same parent');
- target.appendChild(c); // Restore
- assert_array_equals(root.getParts(),[childPartAC]);
-
- target.remove();
- assert_array_equals(root.getParts(),[],'Parent needs to be connected');
- 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>
+}</script>