diff --git a/packages/core/src/compiler/output-targets/standalone/_test_/standalone-types.spec.ts b/packages/core/src/compiler/output-targets/standalone/_test_/standalone-types.spec.ts new file mode 100644 index 00000000000..35c560b2728 --- /dev/null +++ b/packages/core/src/compiler/output-targets/standalone/_test_/standalone-types.spec.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; + +import { stubComponentCompilerMeta } from '../../../types/_tests_/ComponentCompilerMeta.stub'; +import { generateCustomElementType } from '../standalone-types'; + +describe('generateCustomElementType', () => { + it('extends HTMLStencilElement so componentOnReady() is typed', () => { + const cmpMeta = stubComponentCompilerMeta({ tagName: 'my-button' }); + + const result = generateCustomElementType('../types/components', cmpMeta); + + expect(result).toContain('import type { HTMLStencilElement } from "@stencil/core/runtime";'); + expect(result).toContain( + 'interface MyButton extends Components.MyButton, HTMLStencilElement {}', + ); + expect(result).not.toContain('HTMLElement {}'); + }); +}); diff --git a/packages/core/src/compiler/output-targets/standalone/standalone-types.ts b/packages/core/src/compiler/output-targets/standalone/standalone-types.ts index 3d46cc21f37..191cfbf194c 100644 --- a/packages/core/src/compiler/output-targets/standalone/standalone-types.ts +++ b/packages/core/src/compiler/output-targets/standalone/standalone-types.ts @@ -195,15 +195,16 @@ const generateLoaderType = (): string => { * @param cmp the component to generate the type declaration file for * @returns the contents of the type declaration file for the provided `cmp` */ -const generateCustomElementType = ( +export const generateCustomElementType = ( componentsDtsRelPath: string, cmp: d.ComponentCompilerMeta, ): string => { const tagNameAsPascal = dashToPascalCase(cmp.tagName); const o: string[] = [ `import type { Components, JSX } from "${componentsDtsRelPath}";`, + `import type { HTMLStencilElement } from "@stencil/core/runtime";`, ``, - `interface ${tagNameAsPascal} extends Components.${tagNameAsPascal}, HTMLElement {}`, + `interface ${tagNameAsPascal} extends Components.${tagNameAsPascal}, HTMLStencilElement {}`, `export const ${tagNameAsPascal}: {`, ` prototype: ${tagNameAsPascal};`, ` new (): ${tagNameAsPascal};`, diff --git a/test/runtime/src/components/lifecycle/lifecycle-basic/lifecycle-basic.spec.tsx b/test/runtime/src/components/lifecycle/lifecycle-basic/lifecycle-basic.spec.tsx index 5a56776dec5..2be8e76cb8b 100644 --- a/test/runtime/src/components/lifecycle/lifecycle-basic/lifecycle-basic.spec.tsx +++ b/test/runtime/src/components/lifecycle/lifecycle-basic/lifecycle-basic.spec.tsx @@ -38,4 +38,17 @@ describe('lifecycle-basic', () => { expect(updates[4]).toHaveTextContent('componentDidUpdate-b'); expect(updates[5]).toHaveTextContent('componentDidUpdate-a'); }); + + it('componentOnReady does not resolve until nested children have finished loading', async () => { + const { root } = await render(, { waitForReady: false }); + + await customElements.whenDefined('lifecycle-basic-a'); + await (root as any).componentOnReady(); + + const b = root.querySelector('lifecycle-basic-b')!; + const c = root.querySelector('lifecycle-basic-c')!; + + expect(b.classList.contains('hydrated')).toBe(true); + expect(c.classList.contains('hydrated')).toBe(true); + }); }); diff --git a/test/runtime/src/components/lifecycle/lifecycle-nested/lifecycle-nested.spec.tsx b/test/runtime/src/components/lifecycle/lifecycle-nested/lifecycle-nested.spec.tsx index f4b19746953..1c124e5d74a 100644 --- a/test/runtime/src/components/lifecycle/lifecycle-nested/lifecycle-nested.spec.tsx +++ b/test/runtime/src/components/lifecycle/lifecycle-nested/lifecycle-nested.spec.tsx @@ -25,4 +25,20 @@ describe('lifecycle-nested', () => { expect(loads[4]).toHaveTextContent('componentDidLoad-b'); expect(loads[5]).toHaveTextContent('componentDidLoad-c'); }); + + it('componentOnReady on the outermost element waits for slotted nested children', async () => { + const { root } = await render( + '', + { waitForReady: false }, + ); + + await customElements.whenDefined('lifecycle-nested-c'); + await (root as any).componentOnReady(); + + const b = root.querySelector('lifecycle-nested-b')!; + const a = root.querySelector('lifecycle-nested-a')!; + + expect(b.classList.contains('hydrated')).toBe(true); + expect(a.classList.contains('hydrated')).toBe(true); + }); }); diff --git a/test/runtime/vitest.config.ts b/test/runtime/vitest.config.ts index 5638ae55470..6dfda5242fa 100644 --- a/test/runtime/vitest.config.ts +++ b/test/runtime/vitest.config.ts @@ -12,6 +12,7 @@ export default defineVitestConfig({ include: ['src/**/*.spec.{ts,tsx}'], exclude: ['src/scer/**'], setupFiles: ['./vitest-setup-dist.ts'], + retry: 2, env: { TEST_PROJECT: 'dist', }, @@ -30,6 +31,7 @@ export default defineVitestConfig({ include: ['src/**/*.spec.{ts,tsx}'], exclude: ['src/scer/**'], setupFiles: ['./vitest-setup-custom-elements.ts'], + retry: 2, env: { TEST_PROJECT: 'custom-elements', }, @@ -47,6 +49,7 @@ export default defineVitestConfig({ name: 'scer-lazy', include: ['src/scer/**/*.spec.{ts,tsx}'], setupFiles: ['./vitest-setup-scer-lazy.ts'], + retry: 2, browser: { enabled: true, provider: playwright(), @@ -61,6 +64,7 @@ export default defineVitestConfig({ name: 'scer-standalone', include: ['src/scer/**/*.spec.{ts,tsx}'], setupFiles: ['./vitest-setup-scer-standalone.ts'], + retry: 2, browser: { enabled: true, provider: playwright(),