| <!DOCTYPE html> |
| <title>Shadow DOM: Imperative Slot API</title> |
| <meta name="author" title="Yu Han" href="mailto:yuzhehan@chromium.org"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/shadow-dom.js"></script> |
| |
| <div id="test_basic"> |
| <div id="host1"></div> |
| <div id="host2"></div> |
| <div id="host3"></div> |
| </div> |
| <script> |
| test(() => { |
| let tTree = createTestTree(test_basic); |
| const shadow1 = tTree.host1.attachShadow({ mode: 'open', slotAssignment: 'manual'}); |
| assert_not_equals(shadow1, null, 'slot assignment manual should work'); |
| assert_equals(shadow1.slotAssignment, "manual", 'slotAssignment should return "manual"'); |
| const shadow2 = tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'named'}); |
| assert_not_equals(shadow2, null, 'slot assignment named should work'); |
| assert_equals(shadow2.slotAssignment, "named", 'slotAssignment should return "named"'); |
| assert_throws_js(TypeError, () => { |
| tTree.host3.attachShadow({ mode: 'open', slotAssignment: 'exceptional' })}, |
| 'others should throw exception'); |
| }, 'attachShadow can take slotAssignment parameter.'); |
| </script> |
| |
| <div id="test_assign"> |
| <div id="host"> |
| <template id="shadow_root" data-mode="open" data-slot-assignment="manual"> |
| <slot id="s1"></slot> |
| <slot id="s2"></slot> |
| <slot id="s3"></slot> |
| </template> |
| <div id="c1"></div> |
| <div id="c2"></div> |
| <div id="c3"></div> |
| <div id="nested"> |
| <div id="ns1"></div> |
| </div> |
| </div> |
| <div id="c4"></div> |
| <div id="host4"> |
| <template id="shadow_root4" data-mode="open" data-slot-assignment="manual"> |
| <slot id="s4" name="s4"></slot> |
| </template> |
| </div> |
| </div> |
| <script> |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| tTree.s1.assign(c1,c2); // Should work |
| assert_throws_js(TypeError, () => { |
| tTree.s1.assign([c1,c2]) |
| }, 'sequence not allowed'); |
| assert_throws_js(TypeError, () => { |
| tTree.s1.assign([]) |
| }, 'including empty sequences'); |
| }, 'slot.attach() should take variadic not sequence.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| assert_array_equals(tTree.s2.assignedElements(), []); |
| assert_equals(tTree.c1.assignedSlot, null); |
| |
| tTree.s1.assign(tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| |
| tTree.s2.assign(tTree.c2, tTree.c3); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| assert_array_equals(tTree.s2.assignedNodes(), [tTree.c2, tTree.c3]); |
| }, 'Imperative slot API can assign nodes in manual slot assignment.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c2, tTree.c3, tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3, tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| assert_equals(tTree.c3.assignedSlot, tTree.s1); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| assert_equals(tTree.c3.assignedSlot, null); |
| |
| tTree.s1.assign(tTree.c3, tTree.c2, tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c3, tTree.c2, tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| assert_equals(tTree.c3.assignedSlot, tTree.s1); |
| }, 'Order of slottables is preserved in manual slot assignment.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c2, tTree.c3, tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3, tTree.c1]); |
| |
| tTree.s2.assign(tTree.c2); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c3, tTree.c1]); |
| assert_array_equals(tTree.s2.assignedNodes(), [tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s2); |
| assert_equals(tTree.c3.assignedSlot, tTree.s1); |
| |
| tTree.s3.assign(tTree.c3); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| assert_array_equals(tTree.s2.assignedNodes(), [tTree.c2]); |
| assert_array_equals(tTree.s3.assignedNodes(), [tTree.c3]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s2); |
| assert_equals(tTree.c3.assignedSlot, tTree.s3); |
| }, 'Previously assigned slottable is moved to new slot when it\'s reassigned.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1); |
| tTree.s2.assign(tTree.c2, tTree.c1); |
| tTree.s3.assign(tTree.c1, tTree.c3); |
| |
| assert_array_equals(tTree.s1.assignedNodes(), []); |
| assert_array_equals(tTree.s2.assignedNodes(), [tTree.c2]); |
| assert_array_equals(tTree.s3.assignedNodes(), [tTree.c1, tTree.c3]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s3); |
| assert_equals(tTree.c2.assignedSlot, tTree.s2); |
| assert_equals(tTree.c3.assignedSlot, tTree.s3); |
| }, 'Order and assignment of nodes are preserved during multiple assignment in a row.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| // tTree.c4 is invalid for tTree.host slot assignment. |
| // No exception should be thrown here. |
| tTree.s1.assign(tTree.c1, tTree.c4, tTree.c2); |
| |
| // All observable assignments should skip c4. |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| assert_equals(tTree.c4.assignedSlot, null); |
| |
| // Moving c4 into place should reveal the assignment. |
| tTree.host.append(tTree.c4); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c4, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| assert_equals(tTree.c4.assignedSlot, tTree.s1); |
| |
| // Moving c4 into a different shadow host and back should |
| // also not break the assignment. |
| tTree.host4.append(tTree.c4) |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c4.assignedSlot, null); |
| tTree.host.append(tTree.c4); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c4, tTree.c2]); |
| assert_equals(tTree.c4.assignedSlot, tTree.s1); |
| }, 'Assigning invalid nodes should be allowed.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2, tTree.c3); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2, tTree.c3]); |
| |
| tTree.host4.append(tTree.s1); |
| assert_array_equals(tTree.s1.assignedNodes(), []); |
| }, 'Moving a slot to a new host, the slot loses its previously assigned slottables.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2, tTree.c3); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2, tTree.c3]); |
| |
| tTree.shadow_root.insertBefore(tTree.s2, tTree.s1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2, tTree.c3]); |
| assert_array_equals(tTree.s2.assignedNodes(), []); |
| |
| tTree.shadow_root.insertBefore(tTree.s4, tTree.s1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2, tTree.c3]); |
| assert_array_equals(tTree.s4.assignedNodes(), []); |
| |
| tTree.ns1.append(tTree.s1); |
| assert_array_equals(tTree.s1.assignedNodes(), []); |
| }, 'Moving a slot\'s tree order position within a shadow host has no impact on its assigned slottables.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2, tTree.c3); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2, tTree.c3]); |
| |
| tTree.host4.append(tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3]); |
| assert_array_equals(tTree.s4.assignedNodes(), []); |
| assert_equals(tTree.c1.assignedSlot, null); |
| |
| tTree.s4.assign(tTree.c1); |
| assert_array_equals(tTree.s4.assignedNodes(), [tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s4); |
| }, 'Appending slottable to different host, it loses slot assignment. It can be re-assigned within a new host.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| |
| tTree.shadow_root4.insertBefore(tTree.s1, tTree.s4); |
| assert_array_equals(tTree.s1.assignedNodes(), []); |
| // Trigger slot assignment on previous shadow root. |
| assert_array_equals(tTree.s2.assignedNodes(), []); |
| |
| tTree.shadow_root.insertBefore(tTree.s1, tTree.s2); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| }, 'Previously assigned node should not be assigned if slot moved to a new shadow root. The node is re-assigned when moved back.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c1, tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| |
| tTree.s1.assign(tTree.c1, tTree.c1, tTree.c2, tTree.c2, tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| }, 'Assignment with the same node in parameters should be ignored, first one wins.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2, tTree.c3); |
| tTree.s1.remove(); |
| |
| assert_equals(tTree.c1.assignedSlot, null); |
| assert_equals(tTree.c2.assignedSlot, null); |
| assert_equals(tTree.c3.assignedSlot, null); |
| }, 'Removing a slot from DOM resets its slottable\'s slot assignment.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| const isolatedDocNode = document.implementation.createHTMLDocument("").body; |
| isolatedDocNode.appendChild(tTree.c1); |
| const isolatedDocNode2 = document.implementation.createHTMLDocument("").body; |
| isolatedDocNode2.appendChild(tTree.s1); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2); |
| assert_array_equals(tTree.s1.assignedNodes(), [], 's1 not inside shadow root'); |
| assert_equals(tTree.c1.assignedSlot, null); |
| assert_equals(tTree.c2.assignedSlot, null); |
| |
| tTree.shadow_root.appendChild(tTree.s1); |
| tTree.host.appendChild(tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| }, 'Nodes can be assigned even if slots or nodes aren\'t in the same tree.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_assign); |
| |
| tTree.s1.assign(tTree.c1, tTree.c2); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| |
| const isolatedDocNode = document.implementation.createHTMLDocument("").body; |
| isolatedDocNode.appendChild(tTree.c1); |
| const isolatedDocNode2 = document.implementation.createHTMLDocument("").body; |
| isolatedDocNode2.appendChild(tTree.s1); |
| |
| assert_array_equals(tTree.s1.assignedNodes(), [], 's1 not inside shadow root'); |
| assert_equals(tTree.c1.assignedSlot, null); |
| assert_equals(tTree.c2.assignedSlot, null); |
| |
| tTree.shadow_root.appendChild(tTree.s1); |
| tTree.host.appendChild(tTree.c1); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1, tTree.c2]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, tTree.s1); |
| }, 'Removing a node from the document does not break manually assigned slot linkage.'); |
| |
| </script> |