diff --git a/src/hydrate/platform/hydrate-app.ts b/src/hydrate/platform/hydrate-app.ts index fc554b3a27a..f24c9a29e90 100644 --- a/src/hydrate/platform/hydrate-app.ts +++ b/src/hydrate/platform/hydrate-app.ts @@ -120,7 +120,7 @@ export function hydrateApp( // add it to our Set so we know it's already being connected connectedElements.add(elm); - return hydrateComponent(win, results, elm.nodeName, elm, waitingElements); + return hydrateComponent.call(elm, win, results, elm.nodeName, elm, waitingElements); } } @@ -163,6 +163,7 @@ export function hydrateApp( } async function hydrateComponent( + this: HTMLElement, win: Window & typeof globalThis, results: d.HydrateResults, tagName: string, diff --git a/test/end-to-end/src/components.d.ts b/test/end-to-end/src/components.d.ts index 57cf1d21e50..5f7107efecb 100644 --- a/test/end-to-end/src/components.d.ts +++ b/test/end-to-end/src/components.d.ts @@ -49,6 +49,8 @@ export namespace Components { } interface DomVisible { } + interface DsdListenCmp { + } interface ElementCmp { } interface EmptyCmp { @@ -257,6 +259,12 @@ declare global { prototype: HTMLDomVisibleElement; new (): HTMLDomVisibleElement; }; + interface HTMLDsdListenCmpElement extends Components.DsdListenCmp, HTMLStencilElement { + } + var HTMLDsdListenCmpElement: { + prototype: HTMLDsdListenCmpElement; + new (): HTMLDsdListenCmpElement; + }; interface HTMLElementCmpElement extends Components.ElementCmp, HTMLStencilElement { } var HTMLElementCmpElement: { @@ -401,6 +409,7 @@ declare global { "dom-api": HTMLDomApiElement; "dom-interaction": HTMLDomInteractionElement; "dom-visible": HTMLDomVisibleElement; + "dsd-listen-cmp": HTMLDsdListenCmpElement; "element-cmp": HTMLElementCmpElement; "empty-cmp": HTMLEmptyCmpElement; "empty-cmp-shadow": HTMLEmptyCmpShadowElement; @@ -464,6 +473,8 @@ declare namespace LocalJSX { } interface DomVisible { } + interface DsdListenCmp { + } interface ElementCmp { } interface EmptyCmp { @@ -532,6 +543,7 @@ declare namespace LocalJSX { "dom-api": DomApi; "dom-interaction": DomInteraction; "dom-visible": DomVisible; + "dsd-listen-cmp": DsdListenCmp; "element-cmp": ElementCmp; "empty-cmp": EmptyCmp; "empty-cmp-shadow": EmptyCmpShadow; @@ -575,6 +587,7 @@ declare module "@stencil/core" { "dom-api": LocalJSX.DomApi & JSXBase.HTMLAttributes; "dom-interaction": LocalJSX.DomInteraction & JSXBase.HTMLAttributes; "dom-visible": LocalJSX.DomVisible & JSXBase.HTMLAttributes; + "dsd-listen-cmp": LocalJSX.DsdListenCmp & JSXBase.HTMLAttributes; "element-cmp": LocalJSX.ElementCmp & JSXBase.HTMLAttributes; "empty-cmp": LocalJSX.EmptyCmp & JSXBase.HTMLAttributes; "empty-cmp-shadow": LocalJSX.EmptyCmpShadow & JSXBase.HTMLAttributes; diff --git a/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.css b/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.css new file mode 100644 index 00000000000..9ade72b0110 --- /dev/null +++ b/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.css @@ -0,0 +1,3 @@ +:host { + display: block; +} \ No newline at end of file diff --git a/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.tsx b/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.tsx new file mode 100644 index 00000000000..a692755878d --- /dev/null +++ b/test/end-to-end/src/declarative-shadow-dom/dsd-listen-cmp.tsx @@ -0,0 +1,25 @@ +import { Component, Element, h, Host, Listen } from '@stencil/core'; + +@Component({ + tag: 'dsd-listen-cmp', + styleUrl: 'dsd-listen-cmp.css', + shadow: true, +}) +export class MyWhateverComponent { + @Element() hostElement: HTMLSlotElement; + private slotRef: HTMLSlotElement; + + @Listen('keydown', { capture: true }) // Crashes, incorrect binding in hydrate index.js + handleKeyPress(e: CustomEvent): void { + e.stopPropagation(); + console.log(this.slotRef); + } + + render() { + return ( + + (this.slotRef = el)}> + + ); + } +} diff --git a/test/end-to-end/src/declarative-shadow-dom/test.e2e.ts b/test/end-to-end/src/declarative-shadow-dom/test.e2e.ts index bdea1ef7eff..8a1b5b717a8 100644 --- a/test/end-to-end/src/declarative-shadow-dom/test.e2e.ts +++ b/test/end-to-end/src/declarative-shadow-dom/test.e2e.ts @@ -205,4 +205,52 @@ describe('renderToString', () => { const button = await page.find('cmp-server-vs-client'); expect(button.shadowRoot.querySelector('div')).toEqualText('Server vs Client? Winner: Client'); }); + + it('can hydrate components with event listeners', async () => { + const { html } = await renderToString( + ` + Hello World + + `, + { + serializeShadowRoot: true, + fullDocument: false, + }, + ); + + /** + * renders the component with listener with proper vdom annotation, e.g. + * ```html + * + * + * + * Hello World + * + * ``` + */ + + expect(html).toContain( + `Hello World`, + ); + + /** + * renders second component with proper vdom annotation, e.g.: + * ```html + * + * + *
+ * + * 2023 VW Beetle + *
+ *
+ */ + expect(html).toContain( + `
2023 VW Beetle
`, + ); + }); }); diff --git a/test/end-to-end/src/miscellaneous/test.e2e.ts b/test/end-to-end/src/miscellaneous/test.e2e.ts index b9256d5d7ff..960fc73df25 100644 --- a/test/end-to-end/src/miscellaneous/test.e2e.ts +++ b/test/end-to-end/src/miscellaneous/test.e2e.ts @@ -2,6 +2,10 @@ import { type E2EPage, newE2EPage } from '@stencil/core/testing'; let page: E2EPage; +function checkSorted(arr: string[]) { + return arr.every((value, index, array) => index === 0 || value >= array[index - 1]); +} + describe('do not throw page already closed if page was defined in before(All) hook', () => { beforeAll(async () => { page = await newE2EPage(); @@ -38,8 +42,6 @@ describe('sorts hydrated component styles', () => { .split('\n') .map((c) => c.slice(0, c.indexOf('{'))) .find((c) => c.includes('app-root')); - expect(classSelector).toBe( - 'another-car-detail,another-car-list,app-root,build-data,car-detail,car-list,cmp-a,cmp-b,cmp-c,cmp-dsd,cmp-server-vs-client,dom-api,dom-interaction,dom-visible,element-cmp,empty-cmp,empty-cmp-shadow,env-data,event-cmp,import-assets,listen-cmp,method-cmp,path-alias-cmp,prerender-cmp,prop-cmp,scoped-car-detail,scoped-car-list,slot-cmp,slot-cmp-container,slot-parent-cmp,state-cmp', - ); + expect(checkSorted(classSelector.split(','))).toBeTruthy(); }); });