Skip to content

Commit 90caac4

Browse files
committed
test(custom-element): test custom element hydration w/ declarative shadow dom
1 parent 4085def commit 90caac4

File tree

5 files changed

+95
-16
lines changed

5 files changed

+95
-16
lines changed

packages/runtime-core/src/hydration.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,12 @@ const isSVGContainer = (container: Element) =>
6969
const isMathMLContainer = (container: Element) =>
7070
container.namespaceURI!.includes('MathML')
7171

72-
const getContainerType = (container: Element): 'svg' | 'mathml' | undefined => {
73-
if (isSVGContainer(container)) return 'svg'
74-
if (isMathMLContainer(container)) return 'mathml'
72+
const getContainerType = (
73+
container: Element | ShadowRoot,
74+
): 'svg' | 'mathml' | undefined => {
75+
if (container.nodeType !== DOMNodeTypes.ELEMENT) return undefined
76+
if (isSVGContainer(container as Element)) return 'svg'
77+
if (isMathMLContainer(container as Element)) return 'mathml'
7578
return undefined
7679
}
7780

packages/runtime-dom/src/apiCustomElement.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,6 @@ export class VueElement
250250
super()
251251
if (this.shadowRoot && _createApp !== createApp) {
252252
this._root = this.shadowRoot
253-
// TODO hydration needs to be reworked
254-
this._mount(_def)
255253
} else {
256254
if (__DEV__ && this.shadowRoot) {
257255
warn(
@@ -265,10 +263,11 @@ export class VueElement
265263
} else {
266264
this._root = this
267265
}
268-
if (!(this._def as ComponentOptions).__asyncLoader) {
269-
// for sync component defs we can immediately resolve props
270-
this._resolveProps(this._def)
271-
}
266+
}
267+
268+
if (!(this._def as ComponentOptions).__asyncLoader) {
269+
// for sync component defs we can immediately resolve props
270+
this._resolveProps(this._def)
272271
}
273272
}
274273

packages/vue/__tests__/e2e/e2eUtils.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@ import puppeteer, {
55
type PuppeteerLaunchOptions,
66
} from 'puppeteer'
77

8-
export const E2E_TIMEOUT = 30 * 1000
8+
export const E2E_TIMEOUT: number = 30 * 1000
99

1010
const puppeteerOptions: PuppeteerLaunchOptions = {
1111
args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [],
1212
headless: true,
1313
}
1414

1515
const maxTries = 30
16-
export const timeout = (n: number) => new Promise(r => setTimeout(r, n))
16+
export const timeout = (n: number): Promise<any> =>
17+
new Promise(r => setTimeout(r, n))
1718

1819
export async function expectByPolling(
1920
poll: () => Promise<any>,
2021
expected: string,
21-
) {
22+
): Promise<void> {
2223
for (let tries = 0; tries < maxTries; tries++) {
2324
const actual = (await poll()) || ''
2425
if (actual.indexOf(expected) > -1 || tries === maxTries - 1) {
@@ -55,10 +56,7 @@ export function setupPuppeteer(args?: string[]) {
5556
page.on('console', e => {
5657
if (e.type() === 'error') {
5758
const err = e.args()[0]
58-
console.error(
59-
`Error from Puppeteer-loaded page:\n`,
60-
err.remoteObject().description,
61-
)
59+
console.error(`Error from Puppeteer-loaded page:\n`, err.remoteObject())
6260
}
6361
})
6462
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script src="../../dist/vue.global.js"></script>
2+
3+
<my-element
4+
><template shadowrootmode="open"><button>1</button></template></my-element
5+
>
6+
<my-element-async
7+
><template shadowrootmode="open"
8+
><button>1</button></template
9+
></my-element-async
10+
>
11+
12+
<script>
13+
const {
14+
h,
15+
ref,
16+
defineSSRCustomElement,
17+
defineAsyncComponent,
18+
onMounted,
19+
useHost,
20+
} = Vue
21+
22+
const def = {
23+
setup() {
24+
const count = ref(1)
25+
const el = useHost()
26+
onMounted(() => (el.style.border = '1px solid red'))
27+
28+
return () => h('button', { onClick: () => count.value++ }, count.value)
29+
},
30+
}
31+
32+
customElements.define('my-element', defineSSRCustomElement(def))
33+
customElements.define(
34+
'my-element-async',
35+
defineSSRCustomElement(
36+
defineAsyncComponent(
37+
() =>
38+
new Promise(r => {
39+
window.resolve = () => r(def)
40+
}),
41+
),
42+
),
43+
)
44+
</script>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import path from 'node:path'
2+
import { setupPuppeteer } from './e2eUtils'
3+
4+
const { page, click, text } = setupPuppeteer()
5+
6+
// this must be tested in actual Chrome because jsdom does not support
7+
// declarative shadow DOM
8+
test('ssr custom element hydration', async () => {
9+
await page().goto(
10+
`file://${path.resolve(__dirname, './ssr-custom-element.html')}`,
11+
)
12+
13+
function getColor() {
14+
return page().evaluate(() => {
15+
return [
16+
(document.querySelector('my-element') as any).style.border,
17+
(document.querySelector('my-element-async') as any).style.border,
18+
]
19+
})
20+
}
21+
22+
expect(await getColor()).toMatchObject(['1px solid red', ''])
23+
await page().evaluate(() => (window as any).resolve()) // exposed by test
24+
expect(await getColor()).toMatchObject(['1px solid red', '1px solid red'])
25+
26+
async function assertInteraction(el: string) {
27+
const selector = `${el} >>> button`
28+
expect(await text(selector)).toBe('1')
29+
await click(selector)
30+
expect(await text(selector)).toBe('2')
31+
}
32+
33+
await assertInteraction('my-element')
34+
await assertInteraction('my-element-async')
35+
})

0 commit comments

Comments
 (0)