From 57e7e5886d81ba3c3357683415cb592e1e72740d Mon Sep 17 00:00:00 2001 From: John Jenkins Date: Fri, 21 Feb 2025 23:26:34 +0000 Subject: [PATCH] fix(runtime): `scoped: true` slot fallback with forwarded slot (#6171) * fix(runtime): `scoped: true` slot fallback with forwarded slot * chore: --------- Co-authored-by: John Jenkins --- src/runtime/slot-polyfill-utils.ts | 4 +- .../test/hydrate-slot-fallback.spec.tsx | 14 ++--- .../child-component.tsx | 24 +++++++++ .../cmp.test.tsx | 51 +++++++++++++++++++ .../parent-component.tsx | 24 +++++++++ 5 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 test/wdio/slot-fallback-with-forwarded-slot/child-component.tsx create mode 100644 test/wdio/slot-fallback-with-forwarded-slot/cmp.test.tsx create mode 100644 test/wdio/slot-fallback-with-forwarded-slot/parent-component.tsx diff --git a/src/runtime/slot-polyfill-utils.ts b/src/runtime/slot-polyfill-utils.ts index 9c7264e330c..a72f6d2f1cf 100644 --- a/src/runtime/slot-polyfill-utils.ts +++ b/src/runtime/slot-polyfill-utils.ts @@ -26,7 +26,7 @@ export const updateFallbackSlotVisibility = (elm: d.RenderNode) => { getHostSlotNodes(childNodes as any, (elm as HTMLElement).tagName).forEach((slotNode) => { if (slotNode.nodeType === NODE_TYPE.ElementNode && slotNode.tagName === 'SLOT-FB') { // this is a slot fallback node - if (getSlotChildSiblings(slotNode, getSlotName(slotNode), false)?.length) { + if (getSlotChildSiblings(slotNode, getSlotName(slotNode), false).length) { // has slotted nodes, hide fallback slotNode.hidden = true; } else { @@ -108,7 +108,7 @@ export const getSlotChildSiblings = (slot: d.RenderNode, slotName: string, inclu let node = slot; while ((node = node.nextSibling as any)) { - if (getSlotName(node) === slotName) childNodes.push(node as any); + if (getSlotName(node) === slotName && (includeSlot || !node['s-sr'])) childNodes.push(node as any); } return childNodes; }; diff --git a/src/runtime/test/hydrate-slot-fallback.spec.tsx b/src/runtime/test/hydrate-slot-fallback.spec.tsx index 1c079fa50a8..cabaa81161c 100644 --- a/src/runtime/test/hydrate-slot-fallback.spec.tsx +++ b/src/runtime/test/hydrate-slot-fallback.spec.tsx @@ -443,7 +443,7 @@ describe('hydrate, slot fallback', () => { return (
- Fallback content parent - should not be hidden + Fallback content parent - should be hidden
); @@ -458,7 +458,7 @@ describe('hydrate, slot fallback', () => { render() { return (
- Fallback content child - should be hidden + Fallback content child - should not be hidden
); } @@ -479,13 +479,13 @@ describe('hydrate, slot fallback', () => {
-
@@ -507,12 +507,12 @@ describe('hydrate, slot fallback', () => {
- Fallback content child - should be hidden + Fallback content child - should not be hidden
- Fallback content parent - should not be hidden + Fallback content parent - should be hidden diff --git a/test/wdio/slot-fallback-with-forwarded-slot/child-component.tsx b/test/wdio/slot-fallback-with-forwarded-slot/child-component.tsx new file mode 100644 index 00000000000..cac57c7873e --- /dev/null +++ b/test/wdio/slot-fallback-with-forwarded-slot/child-component.tsx @@ -0,0 +1,24 @@ +import { Component, h, Host, Prop } from '@stencil/core'; + +@Component({ + tag: 'slot-forward-child-fallback', + scoped: true, + styles: ` + :host { + display: block; + } + `, +}) +export class ChildComponent { + @Prop() label: string; + + render() { + return ( + +
+ {this.label} +
+
+ ); + } +} diff --git a/test/wdio/slot-fallback-with-forwarded-slot/cmp.test.tsx b/test/wdio/slot-fallback-with-forwarded-slot/cmp.test.tsx new file mode 100644 index 00000000000..0aace2d9c75 --- /dev/null +++ b/test/wdio/slot-fallback-with-forwarded-slot/cmp.test.tsx @@ -0,0 +1,51 @@ +import { h } from '@stencil/core'; +import { render } from '@wdio/browser-runner/stencil'; + +describe('slot-fallback-with-forwarded-slot', () => { + it('renders fallback via prop', async () => { + // @ts-expect-error - wdio complaining about missing prop + const { $root, root } = render({ + template: () => , + }); + await $root.$('slot-fb'); + const fb: HTMLElement = document.querySelector('slot-fb'); + + expect(await $root.getText()).toBe(''); + expect(fb.textContent).toBe('Slot fallback via property'); + expect(fb.getAttribute('hidden')).toBe(null); + expect(fb.hidden).toBe(false); + + const p = document.createElement('p'); + p.textContent = 'Slot content via slot'; + p.slot = 'label'; + root.appendChild(p); + + expect(await $root.getText()).toBe('Slot content via slot'); + expect(fb.getAttribute('hidden')).toBe(''); + expect(fb.hidden).toBe(true); + }); + + it('should hide slot-fb elements when slotted content exists', async () => { + // @ts-expect-error - wdio complaining about missing prop + const { $root, root } = render({ + template: () => ( + +
Slot content via slot
+
+ ), + }); + await $root.$('slot-fb'); + const fb: HTMLElement = document.querySelector('slot-fb'); + + expect(await $root.getText()).toBe('Slot content via slot'); + expect(fb.textContent).toBe('Slot fallback via property'); + expect(fb.getAttribute('hidden')).toBe(''); + expect(fb.hidden).toBe(true); + + root.removeChild(root.childNodes[0]); + + expect(await $root.getText()).toBe(''); + expect(fb.getAttribute('hidden')).toBe(null); + expect(fb.hidden).toBe(false); + }); +}); diff --git a/test/wdio/slot-fallback-with-forwarded-slot/parent-component.tsx b/test/wdio/slot-fallback-with-forwarded-slot/parent-component.tsx new file mode 100644 index 00000000000..af598ccbe93 --- /dev/null +++ b/test/wdio/slot-fallback-with-forwarded-slot/parent-component.tsx @@ -0,0 +1,24 @@ +import { Component, h, Host, Prop } from '@stencil/core'; + +@Component({ + tag: 'slot-forward-root', + scoped: true, + styles: ` + :host { + display: block; + } + `, +}) +export class MyComponent { + @Prop() label: string; + + render() { + return ( + + + + + + ); + } +}