| <!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); |
| assert_not_equals(tTree.host1.attachShadow({ mode: 'open', slotAssignment: 'manual'}), |
| null, 'slot assignment manual should work'); |
| assert_not_equals(tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'auto'}), |
| null, 'slot assignment auto should work'); |
| assert_throws_js(TypeError, () => { |
| tTree.host3.attachShadow({ mode: 'open', slotAssignment: 'exceptional' })}, |
| 'others should throw exception'); |
| }, 'attachShadow can take slotAssignment parameter.'); |
| </script> |
| |
| <div id="test_errors"> |
| <div id="host1"> |
| <template data-mode="open" data-slot-assignment="auto"> |
| <slot id="s1"></slot> |
| </template> |
| <div id="c1"></div> |
| </div> |
| <div id="host2"> |
| <template data-mode="open" data-slot-assignment="manual"> |
| <slot id="s2"></slot> |
| </template> |
| </div> |
| <div id="c2"></div> |
| </div> |
| <script> |
| test(() => { |
| let tTree = createTestTree(test_errors); |
| assert_array_equals(tTree.s1.assignedElements(), [tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| |
| assert_throws_dom('NotAllowedError', () => { tTree.s1.assign([]); }); |
| assert_array_equals(tTree.s1.assignedElements(), [tTree.c1]); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| }, 'Imperative slot API throws exception when not slotAssignment != \'manual\'.'); |
| |
| test(() => { |
| let tTree = createTestTree(test_errors); |
| assert_throws_dom('NotAllowedError', () => { tTree.s2.assign([tTree.c2]); }); |
| |
| assert_throws_dom('NotAllowedError', () => { tTree.s2.assign([tTree.host1]); }); |
| }, 'Imperative slot API throws exception when slottable parentNode != slot\'s host.'); |
| </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); |
| 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. |
| try { |
| tTree.s1.assign([tTree.c1, tTree.c2, tTree.c4]); |
| assert_unreached('assign() should have failed) '); |
| } catch (err) { |
| assert_equals(err.name, 'NotAllowedError'); |
| } |
| |
| assert_array_equals(tTree.s1.assignedNodes(), []); |
| assert_equals(tTree.c1.assignedSlot, null); |
| assert_equals(tTree.c2.assignedSlot, null); |
| assert_equals(tTree.c4.assignedSlot, null); |
| |
| tTree.s1.assign([tTree.c2, tTree.c3, tTree.c1]); |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c2, tTree.c3, tTree.c1]); |
| |
| try { |
| tTree.s1.assign([tTree.c4]); |
| assert_unreached('assign() should have failed) '); |
| } catch (err) { |
| assert_equals(err.name, 'NotAllowedError'); |
| } |
| |
| // Previous state is preserved. |
| 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); |
| }, 'Assigning invalid nodes causes exception and slot returns to its previous state.'); |
| |
| 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(), []); |
| |
| 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(), []); |
| // Don't 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(), []); |
| }, 'Previously assigned node should not be assigned if slot moved to a new shadow root. The slot remains empty when moved back, no trigger recalc.'); |
| |
| 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(), []); |
| }, 'Previously assigned node should not be assigned if slot moved to a new shadow root. The slot remains empty when moved back, trigger recalc.'); |
| |
| 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.c2, tTree.c1]); |
| }, 'Assignment with the same node in parameters should be ignored, last 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); |
| |
| tTree.s1.assign([tTree.c1]); |
| tTree.s2.assign([tTree.c2]); |
| tTree.s3.assign([tTree.c3]); |
| tTree.shadow_root.insertBefore(tTree.s2, tTree.s1); |
| tTree.shadow_root.insertBefore(tTree.s3, tTree.s1); |
| |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| assert_array_equals(tTree.s2.assignedNodes(), []); |
| assert_array_equals(tTree.s3.assignedNodes(), []); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, null); |
| assert_equals(tTree.c3.assignedSlot, null); |
| |
| tTree.s2.remove(); |
| |
| assert_array_equals(tTree.s1.assignedNodes(), [tTree.c1]); |
| assert_array_equals(tTree.s3.assignedNodes(), []); |
| assert_equals(tTree.c1.assignedSlot, tTree.s1); |
| assert_equals(tTree.c2.assignedSlot, null); |
| assert_equals(tTree.c3.assignedSlot, null); |
| }, 'A slot should be cleared of assigned nodes even if it\'s re-inserted into the same shadow root.'); |
| </script> |