test(custom-element): test custom element hydration w/ declarative shadow dom

This commit is contained in:
Evan You 2024-08-10 16:39:20 +08:00
parent 4085def1ba
commit 90caac473d
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
5 changed files with 95 additions and 16 deletions

View File

@ -69,9 +69,12 @@ const isSVGContainer = (container: Element) =>
const isMathMLContainer = (container: Element) =>
container.namespaceURI!.includes('MathML')
const getContainerType = (container: Element): 'svg' | 'mathml' | undefined => {
if (isSVGContainer(container)) return 'svg'
if (isMathMLContainer(container)) return 'mathml'
const getContainerType = (
container: Element | ShadowRoot,
): 'svg' | 'mathml' | undefined => {
if (container.nodeType !== DOMNodeTypes.ELEMENT) return undefined
if (isSVGContainer(container as Element)) return 'svg'
if (isMathMLContainer(container as Element)) return 'mathml'
return undefined
}

View File

@ -250,8 +250,6 @@ export class VueElement
super()
if (this.shadowRoot && _createApp !== createApp) {
this._root = this.shadowRoot
// TODO hydration needs to be reworked
this._mount(_def)
} else {
if (__DEV__ && this.shadowRoot) {
warn(
@ -265,10 +263,11 @@ export class VueElement
} else {
this._root = this
}
if (!(this._def as ComponentOptions).__asyncLoader) {
// for sync component defs we can immediately resolve props
this._resolveProps(this._def)
}
}
if (!(this._def as ComponentOptions).__asyncLoader) {
// for sync component defs we can immediately resolve props
this._resolveProps(this._def)
}
}

View File

@ -5,7 +5,7 @@ import puppeteer, {
type PuppeteerLaunchOptions,
} from 'puppeteer'
export const E2E_TIMEOUT = 30 * 1000
export const E2E_TIMEOUT: number = 30 * 1000
const puppeteerOptions: PuppeteerLaunchOptions = {
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
@ -13,12 +13,13 @@ const puppeteerOptions: PuppeteerLaunchOptions = {
}
const maxTries = 30
export const timeout = (n: number) => new Promise(r => setTimeout(r, n))
export const timeout = (n: number): Promise<any> =>
new Promise(r => setTimeout(r, n))
export async function expectByPolling(
poll: () => Promise<any>,
expected: string,
) {
): Promise<void> {
for (let tries = 0; tries < maxTries; tries++) {
const actual = (await poll()) || ''
if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
@ -55,10 +56,7 @@ export function setupPuppeteer(args?: string[]) {
page.on('console', e => {
if (e.type() === 'error') {
const err = e.args()[0]
console.error(
`Error from Puppeteer-loaded page:\n`,
err.remoteObject().description,
)
console.error(`Error from Puppeteer-loaded page:\n`, err.remoteObject())
}
})
})

View File

@ -0,0 +1,44 @@
<script src="../../dist/vue.global.js"></script>
<my-element
><template shadowrootmode="open"><button>1</button></template></my-element
>
<my-element-async
><template shadowrootmode="open"
><button>1</button></template
></my-element-async
>
<script>
const {
h,
ref,
defineSSRCustomElement,
defineAsyncComponent,
onMounted,
useHost,
} = Vue
const def = {
setup() {
const count = ref(1)
const el = useHost()
onMounted(() => (el.style.border = '1px solid red'))
return () => h('button', { onClick: () => count.value++ }, count.value)
},
}
customElements.define('my-element', defineSSRCustomElement(def))
customElements.define(
'my-element-async',
defineSSRCustomElement(
defineAsyncComponent(
() =>
new Promise(r => {
window.resolve = () => r(def)
}),
),
),
)
</script>

View File

@ -0,0 +1,35 @@
import path from 'node:path'
import { setupPuppeteer } from './e2eUtils'
const { page, click, text } = setupPuppeteer()
// this must be tested in actual Chrome because jsdom does not support
// declarative shadow DOM
test('ssr custom element hydration', async () => {
await page().goto(
`file://${path.resolve(__dirname, './ssr-custom-element.html')}`,
)
function getColor() {
return page().evaluate(() => {
return [
(document.querySelector('my-element') as any).style.border,
(document.querySelector('my-element-async') as any).style.border,
]
})
}
expect(await getColor()).toMatchObject(['1px solid red', ''])
await page().evaluate(() => (window as any).resolve()) // exposed by test
expect(await getColor()).toMatchObject(['1px solid red', '1px solid red'])
async function assertInteraction(el: string) {
const selector = `${el} >>> button`
expect(await text(selector)).toBe('1')
await click(selector)
expect(await text(selector)).toBe('2')
}
await assertInteraction('my-element')
await assertInteraction('my-element-async')
})