Skip to content

Commit

Permalink
fix(renderer): fix conditional rendering issue (#5365)
Browse files Browse the repository at this point in the history
* fix(renderer): fix conditional rendering issue

fix conditional rendering issue by applying to find actual DOM element(s) when about the relocate element is slot ref

fixes one of the issues raised in #5335

* fix(renderer): add check to prevent regression for conditional rendering bugfix

* test(slot): add missing e2e tests for conditional slot rendering
  • Loading branch information
yigityuce committed Mar 13, 2024
1 parent 41f877e commit 5aa886e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 2 deletions.
13 changes: 11 additions & 2 deletions src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,19 @@ const relocateToHostRoot = (parentElm: Element) => {
plt.$flags$ &= ~PLATFORM_FLAGS.isTmpDisconnected;
};

const putBackInOriginalLocation = (parentElm: Node, recursive: boolean) => {
const putBackInOriginalLocation = (parentElm: d.RenderNode, recursive: boolean) => {
plt.$flags$ |= PLATFORM_FLAGS.isTmpDisconnected;
const oldSlotChildNodes: ChildNode[] = Array.from(parentElm.childNodes);

if (parentElm['s-sr'] && BUILD.experimentalSlotFixes) {
let node = parentElm;
while ((node = node.nextSibling as d.RenderNode)) {
if (node && node['s-sn'] === parentElm['s-sn'] && node['s-sh'] === hostTagName) {
oldSlotChildNodes.push(node);
}
}
}

const oldSlotChildNodes = parentElm.childNodes;
for (let i = oldSlotChildNodes.length - 1; i >= 0; i--) {
const childNode = oldSlotChildNodes[i] as any;
if (childNode['s-hn'] !== hostTagName && childNode['s-ol']) {
Expand Down
13 changes: 13 additions & 0 deletions test/karma/test-app/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ export namespace Components {
}
interface SlotChildrenRoot {
}
interface SlotConditionalRendering {
}
interface SlotDynamicNameChangeScoped {
"slotName": string;
}
Expand Down Expand Up @@ -1213,6 +1215,12 @@ declare global {
prototype: HTMLSlotChildrenRootElement;
new (): HTMLSlotChildrenRootElement;
};
interface HTMLSlotConditionalRenderingElement extends Components.SlotConditionalRendering, HTMLStencilElement {
}
var HTMLSlotConditionalRenderingElement: {
prototype: HTMLSlotConditionalRenderingElement;
new (): HTMLSlotConditionalRenderingElement;
};
interface HTMLSlotDynamicNameChangeScopedElement extends Components.SlotDynamicNameChangeScoped, HTMLStencilElement {
}
var HTMLSlotDynamicNameChangeScopedElement: {
Expand Down Expand Up @@ -1603,6 +1611,7 @@ declare global {
"slot-basic-order-root": HTMLSlotBasicOrderRootElement;
"slot-basic-root": HTMLSlotBasicRootElement;
"slot-children-root": HTMLSlotChildrenRootElement;
"slot-conditional-rendering": HTMLSlotConditionalRenderingElement;
"slot-dynamic-name-change-scoped": HTMLSlotDynamicNameChangeScopedElement;
"slot-dynamic-name-change-shadow": HTMLSlotDynamicNameChangeShadowElement;
"slot-dynamic-scoped-list": HTMLSlotDynamicScopedListElement;
Expand Down Expand Up @@ -1949,6 +1958,8 @@ declare namespace LocalJSX {
}
interface SlotChildrenRoot {
}
interface SlotConditionalRendering {
}
interface SlotDynamicNameChangeScoped {
"slotName"?: string;
}
Expand Down Expand Up @@ -2171,6 +2182,7 @@ declare namespace LocalJSX {
"slot-basic-order-root": SlotBasicOrderRoot;
"slot-basic-root": SlotBasicRoot;
"slot-children-root": SlotChildrenRoot;
"slot-conditional-rendering": SlotConditionalRendering;
"slot-dynamic-name-change-scoped": SlotDynamicNameChangeScoped;
"slot-dynamic-name-change-shadow": SlotDynamicNameChangeShadow;
"slot-dynamic-scoped-list": SlotDynamicScopedList;
Expand Down Expand Up @@ -2336,6 +2348,7 @@ declare module "@stencil/core" {
"slot-basic-order-root": LocalJSX.SlotBasicOrderRoot & JSXBase.HTMLAttributes<HTMLSlotBasicOrderRootElement>;
"slot-basic-root": LocalJSX.SlotBasicRoot & JSXBase.HTMLAttributes<HTMLSlotBasicRootElement>;
"slot-children-root": LocalJSX.SlotChildrenRoot & JSXBase.HTMLAttributes<HTMLSlotChildrenRootElement>;
"slot-conditional-rendering": LocalJSX.SlotConditionalRendering & JSXBase.HTMLAttributes<HTMLSlotConditionalRenderingElement>;
"slot-dynamic-name-change-scoped": LocalJSX.SlotDynamicNameChangeScoped & JSXBase.HTMLAttributes<HTMLSlotDynamicNameChangeScopedElement>;
"slot-dynamic-name-change-shadow": LocalJSX.SlotDynamicNameChangeShadow & JSXBase.HTMLAttributes<HTMLSlotDynamicNameChangeShadowElement>;
"slot-dynamic-scoped-list": LocalJSX.SlotDynamicScopedList & JSXBase.HTMLAttributes<HTMLSlotDynamicScopedListElement>;
Expand Down
27 changes: 27 additions & 0 deletions test/karma/test-app/slot-conditional-rendering/cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, h, Host, State } from '@stencil/core';

@Component({
tag: 'slot-conditional-rendering',
shadow: false,
scoped: true,
})
export class SlotConditionalRendering {
@State() headerVisible = true;
@State() contentVisible = true;

render() {
return (
<Host>
{this.headerVisible ? <slot name="header" /> : null}
{this.contentVisible ? <slot /> : null}

<button id="header-visibility-toggle" onClick={() => (this.headerVisible = !this.headerVisible)}>
Toggle header visibility (to {this.headerVisible ? 'hidden' : 'visible'})
</button>
<button id="content-visibility-toggle" onClick={() => (this.contentVisible = !this.contentVisible)}>
Toggle content visibility (to {this.contentVisible ? 'hidden' : 'visible'})
</button>
</Host>
);
}
}
9 changes: 9 additions & 0 deletions test/karma/test-app/slot-conditional-rendering/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="./build/testapp.esm.js" type="module"></script>
<script src="./build/testapp.js" nomodule></script>

<slot-conditional-rendering>
<span slot="header" id="slotted-header-element-id">Hello</span>
<span id="slotted-content-element-id">World!</span>
</slot-conditional-rendering>
39 changes: 39 additions & 0 deletions test/karma/test-app/slot-conditional-rendering/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { setupDomTests, waitForChanges } from '../util';

describe('slot-conditional-rendering', function () {
const { setupDom, tearDownDom } = setupDomTests(document);
let app: HTMLElement;
const getHeaderVisibilityToggle = () => app.querySelector<HTMLButtonElement>('#header-visibility-toggle');
const getContentVisibilityToggle = () => app.querySelector<HTMLButtonElement>('#content-visibility-toggle');
const getHeaderElementInLightDOM = () => app.querySelector('#slotted-header-element-id');
const getContentElementInLightDOM = () => app.querySelector('#slotted-content-element-id');

beforeEach(async () => {
app = await setupDom('/slot-conditional-rendering/index.html');
});
afterEach(tearDownDom);

it('slots are not hidden', async () => {
await waitForChanges();
expect(getHeaderElementInLightDOM().getAttribute('hidden')).toBeNull();
expect(getContentElementInLightDOM().getAttribute('hidden')).toBeNull();
});

it('header slot becomes hidden after hit the toggle button', async () => {
await waitForChanges();
expect(getHeaderElementInLightDOM().getAttribute('hidden')).toBeNull();

getHeaderVisibilityToggle()?.click();
await waitForChanges();
expect(getHeaderElementInLightDOM().getAttribute('hidden')).not.toBeNull();
});

it('content slot becomes hidden after hit the toggle button', async () => {
await waitForChanges();
expect(getContentElementInLightDOM().getAttribute('hidden')).toBeNull();

getContentVisibilityToggle()?.click();
await waitForChanges();
expect(getContentElementInLightDOM().getAttribute('hidden')).not.toBeNull();
});
});

0 comments on commit 5aa886e

Please sign in to comment.