Skip to content

Commit

Permalink
fix(runtime): scoped slot append/prepend correct order after interact…
Browse files Browse the repository at this point in the history
…ion (#5970)

* fix(dom-extras): add required internal flags to the new nodes to work properly with slot relocation algo

#5969

* test: add missing tests

* format(): prettier

---------

Co-authored-by: Tanner Reits <[email protected]>
  • Loading branch information
yigityuce and tanner-reits authored Sep 9, 2024
1 parent 8f0a882 commit 2569abd
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ export const patchSlotAppendChild = (HostElementPrototype: any) => {
const slotName = (newChild['s-sn'] = getSlotName(newChild));
const slotNode = getHostSlotNode(this.childNodes, slotName, this.tagName);
if (slotNode) {
const slotPlaceholder: d.RenderNode = document.createTextNode('') as any;
slotPlaceholder['s-nr'] = newChild;
(slotNode['s-cr'].parentNode as any).__appendChild(slotPlaceholder);
newChild['s-ol'] = slotPlaceholder;
newChild['s-sh'] = slotNode['s-hn'];

const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const appendAfter = slotChildNodes[slotChildNodes.length - 1];
const insertedNode = insertBefore(appendAfter.parentNode, newChild, appendAfter.nextSibling);
Expand Down Expand Up @@ -147,6 +153,7 @@ export const patchSlotPrepend = (HostElementPrototype: HTMLElement) => {
slotPlaceholder['s-nr'] = newChild;
(slotNode['s-cr'].parentNode as any).__appendChild(slotPlaceholder);
newChild['s-ol'] = slotPlaceholder;
newChild['s-sh'] = slotNode['s-hn'];

const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const appendAfter = slotChildNodes[0];
Expand Down
117 changes: 117 additions & 0 deletions test/wdio/scoped-slot-insertion-order-after-interaction/cmp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Fragment, h } from '@stencil/core';
import { render } from '@wdio/browser-runner/stencil';

describe('scoped-slot-insertion-order-after-interaction', () => {
let host: HTMLScopedSlotInsertionOrderAfterInteractionElement;

beforeEach(async () => {
render({
template: () => (
<>
<scoped-slot-insertion-order-after-interaction>
<p>My initial slotted content.</p>
</scoped-slot-insertion-order-after-interaction>

<button type="button" id="appendNodes">
append nodes via "append"
</button>
<button type="button" id="appendChildNodes">
append nodes via "appendChild"
</button>
<button type="button" id="prependNodes">
prepend nodes
</button>
</>
),
});

const scopedSlotInsertionOrderAfterInteraction = document.querySelector(
'scoped-slot-insertion-order-after-interaction',
);

// The element to be inserted
const el = document.createElement('p');
el.innerText = 'The new slotted content.';

await $('#appendNodes').waitForExist();
document.querySelector('#appendNodes').addEventListener('click', () => {
scopedSlotInsertionOrderAfterInteraction.append(el);
});

document.querySelector('#appendChildNodes').addEventListener('click', () => {
scopedSlotInsertionOrderAfterInteraction.appendChild(el);
});

document.querySelector('#prependNodes').addEventListener('click', () => {
scopedSlotInsertionOrderAfterInteraction.prepend(el);
});

host = document.querySelector('scoped-slot-insertion-order-after-interaction');
});

describe('append', () => {
it('inserts a DOM element at the end of the slot', async () => {
expect(host).toBeDefined();

await browser.waitUntil(async () => host.children.length === 1);
expect(host.children[0].textContent).toBe('My initial slotted content.');

const addButton = $('#appendNodes');
await addButton.click();

await browser.waitUntil(async () => host.children.length === 2);
expect(host.children[0].textContent).toBe('My initial slotted content.');
expect(host.children[1].textContent).toBe('The new slotted content.');

const text = $('p');
await text.click();
await browser.waitUntil(async () => host.dataset.counter === '1');
expect(host.children[0].textContent).toBe('My initial slotted content.');
expect(host.children[1].textContent).toBe('The new slotted content.');
});
});

describe('appendChild', () => {
it('inserts a DOM element at the end of the slot', async () => {
expect(host).toBeDefined();

await browser.waitUntil(async () => host.children.length === 1);
expect(host.children[0].textContent).toBe('My initial slotted content.');

const addButton = $('#appendChildNodes');
await addButton.click();

await browser.waitUntil(async () => host.children.length === 2);
expect(host.children[0].textContent).toBe('My initial slotted content.');
expect(host.children[1].textContent).toBe('The new slotted content.');

const text = $('p');
await text.click();
await browser.waitUntil(async () => host.dataset.counter === '1');
expect(host.children[0].textContent).toBe('My initial slotted content.');
expect(host.children[1].textContent).toBe('The new slotted content.');
});
});

describe('prepend', () => {
it('inserts a DOM element at the start of the slot', async () => {
expect(host).toBeDefined();

await browser.waitUntil(async () => host.children.length === 1);
expect(host.children[0].textContent).toBe('My initial slotted content.');

const addButton = $('#prependNodes');
await addButton.click();

await browser.waitUntil(async () => host.children.length === 2);
expect(host.children[0].textContent).toBe('The new slotted content.');
expect(host.children[1].textContent).toBe('My initial slotted content.');

const text = $('p');
await text.click();
await browser.waitUntil(async () => host.dataset.counter === '1');
expect(host.children[0].textContent).toBe('The new slotted content.');
expect(host.children[1].textContent).toBe('My initial slotted content.');
});
});
});
22 changes: 22 additions & 0 deletions test/wdio/scoped-slot-insertion-order-after-interaction/cmp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component, h, Host, State } from '@stencil/core';

@Component({
tag: 'scoped-slot-insertion-order-after-interaction',
scoped: true,
})
export class ScopedSlotInsertionOrderAfterInteraction {
@State() totalCounter = 0;

render() {
return (
<Host
data-counter={this.totalCounter}
onClick={() => {
this.totalCounter = this.totalCounter + 1;
}}
>
<slot></slot>
</Host>
);
}
}

0 comments on commit 2569abd

Please sign in to comment.