Bug 1871195 [wpt PR 43765] - Disallow invalid node types in DOM Parts API constructor...
[gecko.git] / testing / web-platform / tests / dom / parts / basic-dom-part-objects.tentative.html
blobd7834fe69b0118eac16a372dd7b9c14cdd57e575
1 <!DOCTYPE html>
2 <title>DOM Parts: Basic object structure, imperative API</title>
3 <meta name="author" href="mailto:masonf@chromium.org">
4 <script src="/resources/testharness.js"></script>
5 <script src="/resources/testharnessreport.js"></script>
6 <script src="./resources/domparts-utils.js"></script>
8 <body>
9 <template id=imperative>
10 <div>
11 <div id=target1 style="display:none">
12 Imperative test element
13 <span id=a>A</span><span id=b>B
14 <span id=sub>B-sub1</span>
15 <span id=sub>B-sub2</span>
16 </span><span id=c>C</span></div>
17 </div>
18 <span id=direct_child_1></span>
19 <span id=direct_child_2></span>
20 </template>
22 <script>
23 const template = document.getElementById('imperative');
24 function addCleanup(t, part) {
25 t.add_cleanup(() => part.disconnect());
26 return part;
28 [false,true].forEach(useTemplate => {
29 const doc = useTemplate ? template.content : document;
30 let target,wrapper,directChildren;
31 if (useTemplate) {
32 target = doc.querySelector('#target1');
33 directChildren = [doc.querySelector('#direct_child_1'),doc.querySelector('#direct_child_2')];
34 } else {
35 wrapper = document.body.appendChild(document.createElement('div'));
36 wrapper.appendChild(template.content.cloneNode(true));
37 target = wrapper.querySelector('#target1');
38 directChildren = [doc.documentElement,doc.documentElement];
40 const a = target.querySelector('#a');
41 const b = target.querySelector('#b');
42 const c = target.querySelector('#c');
43 assert_true(!!(doc && target && target.parentElement && a && b && c));
44 const description = useTemplate ? "DocumentFragment" : "Document";
45 test((t) => {
46 const root = doc.getPartRoot();
47 assert_true(root instanceof DocumentPartRoot);
48 const parts = root.getParts();
49 assert_equals(parts.length,0,'getParts() should start out empty');
50 assert_true(root.rootContainer instanceof (useTemplate ? DocumentFragment : Document));
52 const nodePart = addCleanup(t,new NodePart(root,target,{metadata: ['foo']}));
53 assertEqualParts([nodePart],[{type:'NodePart',metadata:['foo']}],0,'Basic NodePart');
54 assert_equals(nodePart.node,target);
55 assert_equals(nodePart.root,root);
56 let runningPartsExpectation = [{type:'NodePart',metadata:['foo']}];
57 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart],'getParts() for the root should now have this nodePart');
58 assert_equals(parts.length,0,'Return value of getParts() is not live');
60 assert_throws_js(TypeError,() => new NodePart(nodePart,target.children[0]),'Constructing a Part with a NodePart as the PartRoot should throw');
62 const attributePart = addCleanup(t,new AttributePart(root,target,'attributename',/*automatic*/false,{metadata: ['attribute-non-auto']}));
63 assertEqualParts([attributePart],[{type:'AttributePart',metadata:['attribute-non-auto']}],0,'Basic AttributePart');
64 assert_equals(attributePart.node,target);
65 assert_equals(attributePart.root,root);
66 assert_equals(attributePart.localName,'attributename');
67 assert_equals(attributePart.automatic,false);
68 runningPartsExpectation.push({type:'AttributePart',metadata:['attribute-non-auto']});
69 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart],'getParts() for the root should now have this attributePart');
70 assert_equals(parts.length,0,'Return value of getParts() is not live');
72 const attributePartAuto = addCleanup(t,new AttributePart(root,target,'attributename',/*automatic*/true,{metadata: ['attribute-auto']}));
73 assertEqualParts([attributePartAuto],[{type:'AttributePart',metadata:['attribute-auto']}],0,'Basic automatic AttributePart');
74 assert_equals(attributePartAuto.node,target);
75 assert_equals(attributePartAuto.root,root);
76 assert_equals(attributePartAuto.localName,'attributename');
77 assert_equals(attributePartAuto.automatic,true);
78 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart],'automatic AttributePart should not get included in getParts()');
80 const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['bar','baz']}));
81 assertEqualParts([childNodePart],[{type:'ChildNodePart',metadata:['bar','baz']}],0,'Basic ChildNodePart');
82 assert_equals(childNodePart.root,root);
83 assert_equals(childNodePart.previousSibling,target.children[0]);
84 assert_equals(childNodePart.nextSibling,target.children[2]);
85 assert_equals(childNodePart.getParts().length,0,'childNodePart.getParts() should start out empty');
86 runningPartsExpectation.push({type:'ChildNodePart',metadata:['bar','baz']});
87 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart],'getParts() for the root should now have this childNodePart');
89 const nodeBefore = target.previousSibling || target.parentNode;
90 const nodePartBefore = addCleanup(t,new NodePart(root,nodeBefore));
91 runningPartsExpectation.push({type:'NodePart',metadata:[]});
92 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root should now have this nodePart, in construction order');
94 const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[2],{metadata:['blah']}));
95 assert_equals(nodePart2.root,childNodePart);
96 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root DocumentPartRoot shouldn\'t change');
97 assertEqualParts(childNodePart.getParts(),[{type:'NodePart',metadata:['blah']}],[nodePart2],'getParts() for the childNodePart should have it');
99 nodePart2.disconnect();
100 assert_equals(nodePart2.root,null,'root should be null after disconnect');
101 assert_equals(nodePart2.node,null,'node should be null after disconnect');
102 assert_equals(childNodePart.getParts().length,0,'calling disconnect() should remove the part from root.getParts()');
103 assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root DocumentPartRoot still shouldn\'t change');
104 nodePart2.disconnect(); // Calling twice should be ok.
106 childNodePart.disconnect();
107 assert_equals(childNodePart.root,null,'root should be null after disconnect');
108 assert_equals(childNodePart.previousSibling,null,'previousSibling should be null after disconnect');
109 assert_equals(childNodePart.nextSibling,null,'nextSibling should be null after disconnect');
110 assert_array_equals(root.getParts(),[nodePartBefore,nodePart,attributePart]);
111 }, `Basic imperative DOM Parts object construction (${description})`);
113 function cloneRange(parent,previousSibling,nextSibling) {
114 const clone = parent.cloneNode(false);
115 let node = previousSibling;
116 while (node) {
117 clone.appendChild(node.cloneNode(true));
118 if (node == nextSibling) {
119 break;
121 node = node.nextSibling;
123 return clone;
126 test((t) => {
127 const root = doc.getPartRoot();
128 const nodePart = addCleanup(t,new NodePart(root,target,{metadata:['node1']}));
129 const attributePart = addCleanup(t,new AttributePart(root,target,'attributeName',/*automatic*/false,{metadata: ['attribute']}));
130 const nonTrackedAttributePart = addCleanup(t,new AttributePart(root,target,'attributeName',/*automatic*/true,{metadata: ['attribute-auto']}));
131 const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['child']}));
132 const nodePart3 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 3']}));
133 const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 2']}));
134 const childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild.nextSibling,{metadata: ['childnodepart2']}));
135 let rootExpectations = [{type:'NodePart',metadata:['node1']},{type:'AttributePart',metadata:['attribute']},{type:'ChildNodePart',metadata:['child']}];
136 assertEqualParts(root.getParts(),rootExpectations,[nodePart,attributePart,childNodePart],'setup');
137 let childExpectations = [{type:'NodePart',metadata:['node 3']},{type:'NodePart',metadata:['node 2']},{type:'ChildNodePart',metadata:['childnodepart2']}];
138 assertEqualParts(childNodePart.getParts(),childExpectations,[nodePart3,nodePart2,childNodePart2],'setup');
139 assert_array_equals(childNodePart2.getParts(),[]);
141 // Test cloning of the entire DocumentPartRoot.
142 const clonedPartRoot = root.clone();
143 assertEqualParts(root.getParts(),rootExpectations,[nodePart,attributePart,childNodePart],'cloning a part root should not change the original');
144 const clonedContainer = clonedPartRoot.rootContainer;
145 assert_true(clonedPartRoot instanceof DocumentPartRoot);
146 assert_true(clonedContainer instanceof (useTemplate ? DocumentFragment : Document));
147 assert_not_equals(clonedPartRoot,root);
148 assert_not_equals(clonedContainer,doc);
149 assert_equals(doc.innerHTML,clonedContainer.innerHTML);
150 assertEqualParts(clonedPartRoot.getParts(),rootExpectations,0,'cloned PartRoot should contain identical parts');
151 assert_true(!clonedPartRoot.getParts().includes(nodePart),'Original parts should not be retained');
152 assert_true(!clonedPartRoot.getParts().includes(childNodePart));
153 const newNodePart = clonedPartRoot.getParts()[0];
154 const newAttributePart = clonedPartRoot.getParts()[1];
155 const newChildNodePart = clonedPartRoot.getParts()[2];
156 assert_not_equals(newNodePart.node,target,'Node references should not point to original nodes');
157 assert_equals(newNodePart.node.id,target.id,'New parts should point to cloned nodes');
158 assert_not_equals(newAttributePart.node,target,'Node references should not point to original nodes');
159 assert_equals(newAttributePart.node.id,target.id,'New parts should point to cloned nodes');
160 assert_equals(newAttributePart.localName,attributePart.localName,'New attribute parts should carry over localName');
161 assert_equals(newAttributePart.automatic,attributePart.automatic,'New attribute parts should carry over automatic');
162 assert_not_equals(newChildNodePart.previousSibling,a,'Node references should not point to original nodes');
163 assert_equals(newChildNodePart.previousSibling.id,'a');
164 assert_not_equals(newChildNodePart.nextSibling,c,'Node references should not point to original nodes');
165 assert_equals(newChildNodePart.nextSibling.id,'c');
166 assertEqualParts(newChildNodePart.getParts(),childExpectations,0,'cloned PartRoot should contain identical parts');
168 // Test cloning of ChildNodeParts.
169 const clonedChildNodePartRoot = childNodePart.clone();
170 const clonedChildContainer = clonedChildNodePartRoot.rootContainer;
171 assert_true(clonedChildNodePartRoot instanceof ChildNodePart);
172 assert_true(clonedChildContainer instanceof Element);
173 assert_not_equals(clonedChildContainer,target);
174 assert_equals(clonedChildContainer.outerHTML,cloneRange(target,a,c).outerHTML);
175 assertEqualParts(clonedChildNodePartRoot.getParts(),childExpectations,0,'clone of childNodePart should match');
176 }, `Cloning (${description})`);
178 ['Element','Text','Comment'].forEach(nodeType => {
179 test((t) => {
180 const root = doc.getPartRoot();
181 assert_equals(root.getParts().length,0);
182 let node;
183 switch (nodeType) {
184 case 'Element' : node = document.createElement('div'); break;
185 case 'Text' : node = document.createTextNode('hello'); break;
186 case 'Comment': node = document.createComment('comment'); break;
188 t.add_cleanup(() => node.remove());
189 doc.firstElementChild.append(node);
190 // NodePart
191 const nodePart = addCleanup(t,new NodePart(root,node,{metadata:['foobar']}));
192 assert_true(!!nodePart);
193 const clone = root.clone();
194 assert_equals(clone.getParts().length,1);
195 assertEqualParts(clone.getParts(),[{type:'NodePart',metadata:['foobar']}],0,'getParts');
196 assert_true(clone.getParts()[0].node instanceof window[nodeType]);
198 // ChildNodePart
199 const node2 = node.cloneNode(false);
200 node.parentElement.appendChild(node2);
201 const childNodePart = addCleanup(t,new ChildNodePart(root,node,node2,{metadata:['baz']}));
202 assert_true(!!childNodePart);
203 const clone2 = root.clone();
204 assert_equals(clone2.getParts().length,2);
205 assertEqualParts(clone2.getParts(),[{type:'NodePart',metadata:['foobar']},{type:'ChildNodePart',metadata:['baz']}],0,'getParts2');
206 assert_true(clone2.getParts()[1].previousSibling instanceof window[nodeType]);
207 }, `Cloning ${nodeType} (${description})`);
210 test((t) => {
211 const root = doc.getPartRoot();
212 assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
214 const nodePartB = addCleanup(t,new NodePart(root,b));
215 const nodePartA = addCleanup(t,new NodePart(root,a));
216 const nodePartC = addCleanup(t,new NodePart(root,c));
217 assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Parts can be out of order, if added out of order');
218 b.remove();
219 assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Removals are not tracked');
220 target.parentElement.insertBefore(b,target);
221 assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Insertions are not tracked');
222 target.insertBefore(b,c);
223 assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Nothing is tracked');
224 nodePartA.disconnect();
225 nodePartB.disconnect();
226 nodePartC.disconnect();
227 assert_array_equals(root.getParts(),[],'disconnections are tracked');
229 const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
230 assert_array_equals(root.getParts(),[childPartAC]);
231 a.remove();
232 assert_array_equals(root.getParts(),[],'Removing endpoints invalidates the part');
233 target.insertBefore(a,b); // Restore
234 assert_array_equals(root.getParts(),[],'Insertions are not tracked');
236 target.insertBefore(c,a);
237 assert_array_equals(root.getParts(),[],'Endpoints out of order');
238 target.appendChild(c); // Restore
239 assert_array_equals(root.getParts(),[],'Insertions are not tracked');
241 document.body.appendChild(c);
242 assert_array_equals(root.getParts(),[],'Parts are\'t invalidated when endpoints have different parents');
243 target.appendChild(c); // Restore
244 assert_array_equals(root.getParts(),[],'Insertions are not tracked');
246 const oldParent = target.parentElement;
247 target.remove();
248 assert_array_equals(root.getParts(),[],'Parts are\'t invalidated when disconnected');
249 oldParent.appendChild(target); // Restore
250 assert_array_equals(root.getParts(),[]);
251 }, `DOM mutations are not tracked (${description})`);
253 test((t) => {
254 const root = doc.getPartRoot();
255 assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
256 const otherNode = document.createElement('div');
258 const childPartAA = addCleanup(t,new ChildNodePart(root,a,a));
259 const childPartAB = addCleanup(t,new ChildNodePart(root,a,b));
260 const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
261 assert_throws_dom('InvalidStateError',() => childPartAA.replaceChildren(otherNode),'Can\'t replace children if part is invalid');
262 assert_array_equals(childPartAA.children,[],'Invalid parts should return empty children');
263 assert_array_equals(childPartAB.children,[],'Children should not include endpoints');
264 assert_array_equals(childPartAC.children,[b],'Children should not include endpoints');
265 childPartAB.replaceChildren(otherNode);
266 assert_array_equals(childPartAB.children,[otherNode],'Replacechildren should work');
267 assert_array_equals(childPartAC.children,[otherNode,b],'replaceChildren should leave endpoints alone');
268 childPartAC.replaceChildren(otherNode);
269 assert_array_equals(childPartAC.children,[otherNode],'Replacechildren with existing children should work');
270 assert_array_equals(childPartAB.children,[]);
271 childPartAC.replaceChildren(b);
272 assert_array_equals(target.children,[a,b,c]);
273 }, `ChildNodePart children manipulation (${description})`);
275 test((t) => {
276 const root = doc.getPartRoot();
277 // Make sure no crashes occur for parts with mismatched endpoint nodes.
278 const cornerCasePartsInvalid = [
279 addCleanup(t,new ChildNodePart(root,target, target.children[2],{metadata: ['different parents']})),
280 addCleanup(t,new ChildNodePart(root,target.children[0], target,{metadata: ['different parents']})),
281 addCleanup(t,new ChildNodePart(root,target.children[2], target.children[0],{metadata: ['reversed endpoints']})),
283 const cornerCasePartsValid = [];
284 if (directChildren[0] !== directChildren[1]) {
285 cornerCasePartsValid.push(addCleanup(t,new ChildNodePart(root,directChildren[0], directChildren[1],{metadata: ['direct parent of the root container']})));
287 assert_array_equals(root.getParts(),cornerCasePartsValid);
288 assert_equals(root.clone().getParts().length,cornerCasePartsValid.length);
289 }, `Corner case ChildNodePart construction and cloning (${description})`);
291 wrapper?.remove(); // Cleanup
293 </script>