mirror of https://github.com/vuejs/core.git
test(custom-element): test custom element hydration w/ declarative shadow dom
This commit is contained in:
parent
4085def1ba
commit
90caac473d
|
@ -69,9 +69,12 @@ const isSVGContainer = (container: Element) =>
|
||||||
const isMathMLContainer = (container: Element) =>
|
const isMathMLContainer = (container: Element) =>
|
||||||
container.namespaceURI!.includes('MathML')
|
container.namespaceURI!.includes('MathML')
|
||||||
|
|
||||||
const getContainerType = (container: Element): 'svg' | 'mathml' | undefined => {
|
const getContainerType = (
|
||||||
if (isSVGContainer(container)) return 'svg'
|
container: Element | ShadowRoot,
|
||||||
if (isMathMLContainer(container)) return 'mathml'
|
): '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
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -250,8 +250,6 @@ export class VueElement
|
||||||
super()
|
super()
|
||||||
if (this.shadowRoot && _createApp !== createApp) {
|
if (this.shadowRoot && _createApp !== createApp) {
|
||||||
this._root = this.shadowRoot
|
this._root = this.shadowRoot
|
||||||
// TODO hydration needs to be reworked
|
|
||||||
this._mount(_def)
|
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__ && this.shadowRoot) {
|
if (__DEV__ && this.shadowRoot) {
|
||||||
warn(
|
warn(
|
||||||
|
@ -265,12 +263,13 @@ export class VueElement
|
||||||
} else {
|
} else {
|
||||||
this._root = this
|
this._root = this
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(this._def as ComponentOptions).__asyncLoader) {
|
if (!(this._def as ComponentOptions).__asyncLoader) {
|
||||||
// for sync component defs we can immediately resolve props
|
// for sync component defs we can immediately resolve props
|
||||||
this._resolveProps(this._def)
|
this._resolveProps(this._def)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
if (!this.shadowRoot) {
|
if (!this.shadowRoot) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import puppeteer, {
|
||||||
type PuppeteerLaunchOptions,
|
type PuppeteerLaunchOptions,
|
||||||
} from 'puppeteer'
|
} from 'puppeteer'
|
||||||
|
|
||||||
export const E2E_TIMEOUT = 30 * 1000
|
export const E2E_TIMEOUT: number = 30 * 1000
|
||||||
|
|
||||||
const puppeteerOptions: PuppeteerLaunchOptions = {
|
const puppeteerOptions: PuppeteerLaunchOptions = {
|
||||||
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
|
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
|
||||||
|
@ -13,12 +13,13 @@ const puppeteerOptions: PuppeteerLaunchOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxTries = 30
|
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(
|
export async function expectByPolling(
|
||||||
poll: () => Promise<any>,
|
poll: () => Promise<any>,
|
||||||
expected: string,
|
expected: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
for (let tries = 0; tries < maxTries; tries++) {
|
for (let tries = 0; tries < maxTries; tries++) {
|
||||||
const actual = (await poll()) || ''
|
const actual = (await poll()) || ''
|
||||||
if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
|
if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
|
||||||
|
@ -55,10 +56,7 @@ export function setupPuppeteer(args?: string[]) {
|
||||||
page.on('console', e => {
|
page.on('console', e => {
|
||||||
if (e.type() === 'error') {
|
if (e.type() === 'error') {
|
||||||
const err = e.args()[0]
|
const err = e.args()[0]
|
||||||
console.error(
|
console.error(`Error from Puppeteer-loaded page:\n`, err.remoteObject())
|
||||||
`Error from Puppeteer-loaded page:\n`,
|
|
||||||
err.remoteObject().description,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
|
@ -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')
|
||||||
|
})
|
Loading…
Reference in New Issue