Skip to content

Commit

Permalink
fix(hyrdate): support vdom annotation in nested dsd structures (#5856)
Browse files Browse the repository at this point in the history
* fix(hyrdate): support vdom annotation in nested dsd structures

* prettier

* update snapshots

* prettier
  • Loading branch information
christian-bromann authored Jun 26, 2024
1 parent 850ad4f commit 61bb5e3
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/hydrate/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const win = window;
export const doc = win.document;

export const readTask = (cb: Function) => {
process.nextTick(() => {
nextTick(() => {
try {
cb();
} catch (e) {
Expand All @@ -66,7 +66,7 @@ export const readTask = (cb: Function) => {
};

export const writeTask = (cb: Function) => {
process.nextTick(() => {
nextTick(() => {
try {
cb();
} catch (e) {
Expand Down
7 changes: 6 additions & 1 deletion src/runtime/vdom/vdom-annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ const parseVNodeAnnotations = (
}

if (node.nodeType === NODE_TYPE.ElementNode) {
node.childNodes.forEach((childNode) => {
/**
* we need to insert the vnode annotations on the host element children as well
* as on the children from its shadowRoot if there is one
*/
const childNodes = [...Array.from(node.childNodes), ...Array.from(node.shadowRoot?.childNodes || [])];
childNodes.forEach((childNode) => {
const hostRef = getHostRef(childNode);
if (hostRef != null && !docData.staticComponents.has(childNode.nodeName.toLowerCase())) {
const cmpData: CmpData = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-car-list\\">/*!@:host*/.sc-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-car-list\\" c-id=\\"1.1.1.0\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\"><!----><section>2024 VW Vento</section></car-detail></li><li class=\\"sc-car-list\\" c-id=\\"1.3.1.1\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\"><!----><section>2023 VW Beetle</section></car-detail></li></ul></template><!--r.1--></car-list>"`;
exports[`renderToString can render a scoped component within a shadow component 1`] = `"<car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-car-list\\">/*!@:host*/.sc-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-car-list\\" c-id=\\"1.1.1.0\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><!--r.2--><section c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail></li><li class=\\"sc-car-list\\" c-id=\\"1.3.1.1\\"><car-detail class=\\"sc-car-list\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><!--r.3--><section c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail></li></ul></template><!--r.1--></car-list>"`;

exports[`renderToString can render nested components 1`] = `"<another-car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-another-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-list\\">/*!@:host*/.sc-another-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-another-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-another-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-another-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-another-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-another-car-list\\" c-id=\\"1.1.1.0\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\">2024 VW Vento</section></template><!----></another-car-detail></li><li class=\\"sc-another-car-list\\" c-id=\\"1.3.1.1\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\">2023 VW Beetle</section></template><!----></another-car-detail></li></ul></template><!--r.1--></another-car-list>"`;
exports[`renderToString can render nested components 1`] = `"<another-car-list cars=\\"[{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Vento&quot;,&quot;year&quot;:2024},{&quot;make&quot;:&quot;VW&quot;,&quot;model&quot;:&quot;Beetle&quot;,&quot;year&quot;:2023}]\\" class=\\"sc-another-car-list-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-list\\">/*!@:host*/.sc-another-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}/*!@ul*/ul.sc-another-car-list{display:block;margin:0;padding:0}/*!@li*/li.sc-another-car-list{list-style:none;margin:0;padding:20px}/*[email protected]*/.selected.sc-another-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><ul class=\\"sc-another-car-list\\" c-id=\\"1.0.0.0\\"><li class=\\"sc-another-car-list\\" c-id=\\"1.1.1.0\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.2.2.0\\" s-id=\\"2\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\" c-id=\\"2.0.0.0\\"><!--t.2.1.1.0-->2024 VW Vento</section></template><!--r.2--></another-car-detail></li><li class=\\"sc-another-car-list\\" c-id=\\"1.3.1.1\\"><another-car-detail class=\\"sc-another-car-list sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" c-id=\\"1.4.2.0\\" s-id=\\"3\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style><section class=\\"sc-another-car-detail\\" c-id=\\"3.0.0.0\\"><!--t.3.1.1.0-->2023 VW Beetle</section></template><!--r.3--></another-car-detail></li></ul></template><!--r.1--></another-car-list>"`;

exports[`renderToString renders scoped component 1`] = `"<another-car-detail class=\\"sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" s-id=\\"1\\"><template shadowrootmode=\\"open\\"><style sty-id=\\"sc-another-car-detail\\">/*!@section*/section.sc-another-car-detail{color:green}</style></template><!--r.1--></another-car-detail>"`;
Expand Down
8 changes: 4 additions & 4 deletions test/end-to-end/src/declarative-shadow-dom/test.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,18 @@ describe('renderToString', () => {
});
expect(html).toMatchSnapshot();
expect(html).toContain(
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0"><!----><section>2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
);
expect(html).toContain(
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0"><!----><section>2023 VW Beetle</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
);
});

it('can render a scoped component within a shadow component (sync)', async () => {
const input = `<car-list cars=${JSON.stringify([vento, beetle])}></car-list>`;
const expectedResults = [
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0"><!----><section>2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0"><!----><section>2023 VW Beetle</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.2.2.0" s-id="2"><!--r.2--><section c-id="2.0.0.0"><!--t.2.1.1.0-->2024 VW Vento</section></car-detail>',
'<car-detail class="sc-car-list" custom-hydrate-flag="" c-id="1.4.2.0" s-id="3"><!--r.3--><section c-id="3.0.0.0"><!--t.3.1.1.0-->2023 VW Beetle</section></car-detail>',
] as const;
const opts = {
serializeShadowRoot: true,
Expand Down
3 changes: 3 additions & 0 deletions test/wdio/declarative-shadow-dom/page-list-item.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: block;
}
31 changes: 31 additions & 0 deletions test/wdio/declarative-shadow-dom/page-list-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, h, Prop } from '@stencil/core';

@Component({
tag: 'page-list-item',
styleUrl: 'page-list-item.css',
shadow: true,
})
export class MyOtherComponent {
/**
* Set the number to be displayed.
*/
@Prop() label!: number;

/**
* Set the number to be displayed.
*/
@Prop() active = false;

render() {
const paginationItemClass: any = {
'pagination-item': true,
active: this.active,
};

return (
<div>
<div class={paginationItemClass}>{this.label}</div>
</div>
);
}
}
3 changes: 3 additions & 0 deletions test/wdio/declarative-shadow-dom/page-list.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: block;
}
40 changes: 40 additions & 0 deletions test/wdio/declarative-shadow-dom/page-list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @ts-ignore may not be existing when project hasn't been built
type HydrateModule = typeof import('../../hydrate');
let renderToString: HydrateModule['renderToString'];

describe('renderToString', () => {
before(async () => {
// @ts-ignore may not be existing when project hasn't been built
const mod = await import('/hydrate/index.mjs');
renderToString = mod.renderToString;
});

beforeEach(async () => {
const { html } = await renderToString(`<page-list last-page="5" current-page="1"></page-list>`, {
serializeShadowRoot: true,
prettyHtml: true,
fullDocument: false,
});
const stage = document.createElement('div');
stage.setAttribute('id', 'stage');
stage.setHTMLUnsafe(html);
document.body.appendChild(stage);
});

afterEach(() => {
document.querySelector('#stage')?.remove();
});

it('can hydrate a nested shadow component', async () => {
expect(typeof customElements.get('page-list-item')).toBe('undefined');

// @ts-expect-error resolved through WDIO
const { defineCustomElements } = await import('/dist/loader/index.js');
defineCustomElements().catch(console.error);

// wait for Stencil to take over and reconcile
await browser.waitUntil(async () => customElements.get('page-list-item'));
expect(typeof customElements.get('page-list-item')).toBe('function');
await expect($('page-list')).toHaveText('0\n1\n2\n3\n4');
});
});
39 changes: 39 additions & 0 deletions test/wdio/declarative-shadow-dom/page-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Component, h, Prop, State } from '@stencil/core';

@Component({
tag: 'page-list',
styleUrl: 'page-list.css',
shadow: true,
})
export class PatternlibPagination {
@Prop({ mutable: true }) lastPage: number | null = null;
@State() pages: Array<number> = [];

private fillPageArray(start: number, num: number): Array<number> {
const pages = [];
for (let i = 0; i < num; i++) {
pages.push(start + i);
}
return pages;
}

componentWillLoad(): void {
// range guard
this.lastPage = this.lastPage && this.lastPage >= 1 ? this.lastPage : 1;
this.pages = this.fillPageArray(0, this.lastPage);
}

render() {
return (
<div>
<div class="pagination">
<div class="pagination-pages pagination-notation">
{this.pages.map((i) => (
<page-list-item label={i}></page-list-item>
))}
</div>
</div>
</div>
);
}
}
3 changes: 2 additions & 1 deletion test/wdio/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const testRequiresManualSetup =
window.__wdioSpec__.includes('custom-elements-delegates-focus') ||
window.__wdioSpec__.includes('custom-elements-output') ||
window.__wdioSpec__.includes('global-script') ||
window.__wdioSpec__.endsWith('custom-tag-name.test.tsx');
window.__wdioSpec__.endsWith('custom-tag-name.test.tsx') ||
window.__wdioSpec__.endsWith('page-list.test.ts');

/**
* setup all components defined in tests except for those where we want ot manually setup
Expand Down

0 comments on commit 61bb5e3

Please sign in to comment.