blob: 7b051f285b35ceecd727145e839866b00fa14a67 [file] [log] [blame]
<!DOCTYPE html>
<title>DOM Parts: Basic object structure</title>
<meta name="author" href="mailto:masonf@chromium.org">
<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-sub</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>
<script>
const template = document.getElementById('template');
document.body.appendChild(template.content.cloneNode(true));
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));
const description = useTemplate ? "DocumentFragment" : "Document";
test((t) => {
const root = doc.getPartRoot();
assert_true(root instanceof DocumentPartRoot);
const parts = root.getParts();
assert_equals(parts.length,0,'getParts() should start out empty');
assert_true(root.rootContainer instanceof (useTemplate ? DocumentFragment : Document));
const nodePart = addCleanup(t,new NodePart(root,target,{metadata: ['foo']}));
assert_true(nodePart instanceof 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);
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);
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);
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');
const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[2]));
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]);
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');
nodePart2.disconnect(); // Calling twice should be ok.
childNodePart.disconnect();
assert_equals(childNodePart.root,null,'root should be null after disconnect');
assert_equals(childNodePart.previousSibling,null,'previousSibling should be null after disconnect');
assert_equals(childNodePart.nextSibling,null,'nextSibling should be null after disconnect');
assert_array_equals(root.getParts(),[nodePartBefore,nodePart]);
}, `Basic imperative DOM Parts object construction (${description})`);
function cloneRange(parent,previousSibling,nextSibling) {
const clone = parent.cloneNode(false);
let node = previousSibling;
while (node) {
clone.appendChild(node.cloneNode(true));
if (node == nextSibling) {
break;
}
node = node.nextSibling;
}
return clone;
}
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 childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild,{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(),[]);
// Test cloning of the entire DocumentPartRoot.
const clonedPartRoot = root.clone();
const clonedContainer = clonedPartRoot.rootContainer;
assert_true(clonedPartRoot instanceof DocumentPartRoot);
assert_true(clonedContainer instanceof (useTemplate ? DocumentFragment : Document));
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]);
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_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);
// Test cloning of ChildNodeParts.
const clonedChildNodePartRoot = childNodePart.clone();
const clonedChildContainer = clonedChildNodePartRoot.rootContainer;
assert_true(clonedChildNodePartRoot instanceof ChildNodePart);
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);
}, `Cloning (${description})`);
['Element','Text','Comment','CDATASection','ProcessingInstruction'].forEach(nodeType => {
test((t) => {
const root = doc.getPartRoot();
assert_equals(root.getParts().length,0);
let node;
switch (nodeType) {
case 'Element' : node = document.createElement('div'); break;
case 'Text' : node = document.createTextNode('hello'); break;
case 'Comment': node = document.createComment('comment'); break;
case 'CDATASection':
const tempdoc = (new DOMParser()).parseFromString("<xml></xml>", "application/xml");
node = tempdoc.createCDATASection("CDATA");
break;
case 'ProcessingInstruction': node = document.createProcessingInstruction('target','data'); break;
}
t.add_cleanup(() => node.remove());
doc.firstElementChild.append(node);
const nodePart = addCleanup(t,new NodePart(root,node));
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]);
}, `Cloning ${nodeType} (${description})`);
});
});
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');
</script>