basic-dom-part-declarative-brace-syntax.tentative.html (9305B)
1 <!DOCTYPE html> 2 <title>DOM Parts: Basic object structure, {{}} declarative API</title> 3 <link rel=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> 7 8 <div> 9 <!-- Note - the test will remove this chunk of DOM once the test completes --> 10 <div id=target2> 11 Declarative syntax - The *two* templates below should have IDENTICAL STRUCTURE 12 to this one. There are four cases to test: 13 1. Main document parsing (this chunk) 14 2. Template parsing (the template below with id=declarative) 15 3. Template/fragment cloning (a clone of the template with id=declarative) 16 4. Declarative Shadow DOM parsing (template with id=declarative_shadow_dom and shadowrootmode attribute) 17 <h1 id="name" parseparts> 18 {{#}} 19 First 20 {{#}} <span {{}} id={{}}>Middle</span> {{/}} 21 Last 22 {{/}} 23 <a foo {{}} id=nodepart1>content</a> 24 <a {{}} id=nodepart2>content</a> 25 <a {{}}id=nodepart3>content</a> 26 <a id=nodepart4 {{}}>content</a> 27 <a id=nodepart5 foo {{}}>content</a> 28 <a id=nodepart6 foo {{}} >content</a> 29 </h1> 30 </div> 31 </div> 32 <template id=declarative> 33 <div> 34 <div id=target3>Declarative syntax 35 <h1 id="name" parseparts> 36 {{#}} 37 First 38 {{#}} <span {{}} id={{}}>Middle</span> {{/}} 39 Last 40 {{/}} 41 <a foo {{}} id=nodepart1>content</a> 42 <a {{}} id=nodepart2>content</a> 43 <a {{}}id=nodepart3>content</a> 44 <a id=nodepart4 {{}}>content</a> 45 <a id=nodepart5 foo {{}}>content</a> 46 <a id=nodepart6 foo {{}} >content</a> 47 </h1> 48 </div> 49 </div> 50 </template> 51 52 <!-- TODO: This test should look at declarative shadow DOM behavior. --> 53 54 <script> { 55 function addPartsCleanup(t,partRoot) { 56 t.add_cleanup(() => partRoot.getParts().forEach(part => part.disconnect())); 57 } 58 const kChildNodePartStartCommentData = "#"; 59 const kChildNodePartEndCommentData = "/"; 60 function assertIsComment(node, commentText) { 61 assert_true(node instanceof Comment); 62 // TODO(crbug.com/40271855): While developing alternative syntax, the comment might be empty or it might be "#" or "/". 63 assert_true(node.textContent === '' || node.textContent === commentText); 64 } 65 66 const template = document.getElementById('declarative'); 67 ['Main Document','Template','Clone','PartClone'].forEach(testCase => { 68 test((t) => { 69 let doc,target,wrapper,cleanup; 70 let expectDOMParts = true; 71 switch (testCase) { 72 case 'Main Document': 73 doc = document; 74 target = doc.querySelector('#target2'); 75 cleanup = [target.parentElement]; 76 break; 77 case 'Template': 78 doc = template.content; 79 target = doc.querySelector('#target3'); 80 cleanup = []; 81 break; 82 case 'Clone': 83 doc = document; 84 wrapper = document.body.appendChild(document.createElement('div')); 85 wrapper.appendChild(template.content.cloneNode(true)); 86 target = wrapper.querySelector('#target3'); 87 // A "normal" tree clone should not keep DOM Parts: 88 expectDOMParts = false; 89 cleanup = [wrapper]; 90 break; 91 case 'PartClone': 92 doc = document; 93 wrapper = document.body.appendChild(document.createElement('div')); 94 wrapper.appendChild(template.content.getPartRoot().clone().rootContainer); 95 target = wrapper.querySelector('#target3'); 96 // Even a PartRoot clone should not add parts to the document, when that 97 // clone is appendChilded to the document. 98 expectDOMParts = false; 99 cleanup = [wrapper]; 100 break; 101 default: 102 assert_unreached('Invalid test case'); 103 } 104 assert_true(!!(doc && target && target.parentElement)); 105 106 const root = doc.getPartRoot(); 107 t.add_cleanup(() => cleanup.forEach(el => el.remove())); // Cleanup 108 addPartsCleanup(t,root); // Clean up all Parts when this test ends. 109 110 assert_true(root instanceof DocumentPartRoot); 111 if (expectDOMParts) { 112 let expectedRootParts = [{type:'ChildNodePart',metadata:[]}]; 113 for(let i=0;i<6;++i) { 114 expectedRootParts.push({type:'NodePart',metadata:[]}); 115 } 116 assertEqualParts(root.getParts(),expectedRootParts,0,'declarative root missing parts'); 117 for(let i=1;i<=6;++i) { 118 assert_equals(root.getParts()[i].node,target.querySelector(`#nodepart${i}`)); 119 } 120 const childPart1 = root.getParts()[0]; 121 assertIsComment(childPart1.previousSibling,kChildNodePartStartCommentData); 122 assertIsComment(childPart1.nextSibling,kChildNodePartEndCommentData); 123 const expectedChild1Parts = [{type:'ChildNodePart',metadata:[]}]; 124 assertEqualParts(childPart1.getParts(),expectedChild1Parts,0,'First level childpart should just have one child part'); 125 const childPart2 = childPart1.getParts()[0]; 126 assertIsComment(childPart2.previousSibling,kChildNodePartStartCommentData); 127 assertIsComment(childPart2.nextSibling,kChildNodePartEndCommentData); 128 const expectedChild2Parts = [{type:'NodePart',metadata:[]},{type:'AttributePart',metadata:[]}]; 129 assertEqualParts(childPart2.getParts(),expectedChild2Parts,0,'Second level childpart should have NodePart and AttributePart'); 130 assert_true(childPart2.getParts()[0].node instanceof HTMLSpanElement); 131 assert_equals(childPart2.getParts()[0].node.textContent,'Middle'); 132 assert_true(childPart2.getParts()[1].node instanceof HTMLSpanElement); 133 assert_equals(childPart2.getParts()[1].node.textContent,'Middle'); 134 } else { 135 assertEqualParts(root.getParts(),[],[]); 136 } 137 }, `Basic declarative DOM Parts (${testCase})`); 138 }); 139 140 }</script> 141 142 <div parseparts>Before {{#}}Parts{{/}} After</div> 143 <script> 144 test((t) => { 145 const target = document.currentScript.previousElementSibling; 146 t.add_cleanup(() => target.remove()); 147 const root = document.getPartRoot(); 148 addPartsCleanup(t,root); 149 assert_equals(root.getParts().length,1); 150 assert_equals(target.innerHTML,'Before <!---->Parts<!----> After'); 151 152 // Verify that removing the parseparts attribute does nothing. 153 target.removeAttribute('parseparts'); 154 assert_equals(root.getParts().length,1); 155 assert_equals(target.innerHTML,'Before <!---->Parts<!----> After'); 156 }, 'Post-parsing structure of child parts, and stickiness'); 157 </script> 158 159 <div>Before {{#}}Parts{{/}} After</div> 160 <script>{ 161 test((t) => { 162 const target = document.currentScript.previousElementSibling; 163 t.add_cleanup(() => target.remove()); 164 const root = document.getPartRoot(); 165 addPartsCleanup(t,root); 166 assert_equals(root.getParts().length,0); 167 assert_equals(target.innerHTML,'Before {{#}}Parts{{/}} After'); 168 target.setAttribute('parseparts',''); 169 assert_equals(root.getParts().length,0); 170 assert_equals(target.innerHTML,'Before {{#}}Parts{{/}} After'); 171 }, 'Parser only behavior - adding parseparts does nothing'); 172 }</script> 173 174 <div parseparts>{{#}}{{/}}</div> 175 <script>{ 176 test((t) => { 177 const target = document.currentScript.previousElementSibling; 178 t.add_cleanup(() => target.remove()); 179 const root = document.getPartRoot(); 180 addPartsCleanup(t,root); 181 assert_equals(root.getParts().length,1); 182 assert_equals(target.innerHTML,'<!----><!---->'); 183 }, 'Just parts, no text before'); 184 }</script> 185 186 <div><input parseparts>{{#}}{{/}}</input></div> 187 <script>{ 188 test((t) => { 189 const target = document.currentScript.previousElementSibling; 190 t.add_cleanup(() => target.remove()); 191 const root = document.getPartRoot(); 192 addPartsCleanup(t,root); 193 assert_equals(root.getParts().length,0); 194 assert_equals(target.innerHTML,'<input parseparts=\"\">{{#}}{{/}}'); 195 }, 'Self closing elements can\'t use parseparts'); 196 }</script> 197 198 <div><head parseparts>{{#}}{{/}}</head></div> 199 <script>{ 200 test((t) => { 201 const target = document.currentScript.previousElementSibling; 202 t.add_cleanup(() => target.remove()); 203 const root = document.getPartRoot(); 204 addPartsCleanup(t,root); 205 assert_equals(root.getParts().length,0); 206 assert_equals(target.innerHTML,'{{#}}{{/}}'); 207 }, 'Second head element can\'t use parseparts'); 208 }</script> 209 210 <div parseparts><svg>{{#}}<circle/>{{/}}</svg></div> 211 212 <script>{ 213 test((t) => { 214 const target = document.currentScript.previousElementSibling; 215 t.add_cleanup(() => target.remove()); 216 const root = document.getPartRoot(); 217 addPartsCleanup(t,root); 218 assert_equals(root.getParts().length,1); 219 assert_equals(target.innerHTML,'<svg><!----><circle/><!----></svg>'); 220 }, 'Foreign content should support Parts'); 221 }</script> 222 223 <div> 224 <div parseparts>{{}}{{ }}{{ #}}{{ /}}{{{}}}</div> 225 <div>{{}}{{ }}{{ #}}{{ /}}{{{}}}</div> 226 </div> 227 <script>{ 228 test((t) => { 229 const target = document.currentScript.previousElementSibling; 230 t.add_cleanup(() => target.remove()); 231 const root = document.getPartRoot(); 232 addPartsCleanup(t,root); 233 assert_equals(root.getParts().length,0); 234 assert_equals(target.childElementCount,2); 235 Array.from(target.children).forEach(el => { 236 assert_equals(el.innerHTML,'{{}}{{ }}{{ #}}{{ /}}{{{}}}'); 237 }) 238 }, 'Not quite parts syntax - none should become parts, and nothing should crash'); 239 }</script>