Skip to content

Commit b74bab2

Browse files
committed
feat(portal): hydration support for portal disabled mode
1 parent 9ed9bf3 commit b74bab2

File tree

3 files changed

+244
-117
lines changed

3 files changed

+244
-117
lines changed

packages/runtime-core/__tests__/hydration.spec.ts

+82-9
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,26 @@ describe('SSR hydration', () => {
161161
portalContainer.innerHTML = `<span>foo</span><span class="foo"></span><!---->`
162162
document.body.appendChild(portalContainer)
163163

164-
const { vnode, container } = mountWithHydration('<!--portal-->', () =>
165-
h(Portal, { target: '#portal' }, [
166-
h('span', msg.value),
167-
h('span', { class: msg.value, onClick: fn })
168-
])
164+
const { vnode, container } = mountWithHydration(
165+
'<!--portal start--><!--portal end-->',
166+
() =>
167+
h(Portal, { target: '#portal' }, [
168+
h('span', msg.value),
169+
h('span', { class: msg.value, onClick: fn })
170+
])
169171
)
170172

171173
expect(vnode.el).toBe(container.firstChild)
174+
expect(vnode.anchor).toBe(container.lastChild)
175+
176+
expect(vnode.target).toBe(portalContainer)
172177
expect((vnode.children as VNode[])[0].el).toBe(
173178
portalContainer.childNodes[0]
174179
)
175180
expect((vnode.children as VNode[])[1].el).toBe(
176181
portalContainer.childNodes[1]
177182
)
183+
expect(vnode.targetAnchor).toBe(portalContainer.childNodes[2])
178184

179185
// event handler
180186
triggerEvent('click', portalContainer.querySelector('.foo')!)
@@ -208,7 +214,7 @@ describe('SSR hydration', () => {
208214
const ctx: SSRContext = {}
209215
const mainHtml = await renderToString(h(Comp), ctx)
210216
expect(mainHtml).toMatchInlineSnapshot(
211-
`"<!--[--><!--portal--><!--portal--><!--]-->"`
217+
`"<!--[--><!--portal start--><!--portal end--><!--portal start--><!--portal end--><!--]-->"`
212218
)
213219

214220
const portalHtml = ctx.portals!['#portal2']
@@ -224,16 +230,21 @@ describe('SSR hydration', () => {
224230
const portalVnode1 = (vnode.children as VNode[])[0]
225231
const portalVnode2 = (vnode.children as VNode[])[1]
226232
expect(portalVnode1.el).toBe(container.childNodes[1])
227-
expect(portalVnode2.el).toBe(container.childNodes[2])
233+
expect(portalVnode1.anchor).toBe(container.childNodes[2])
234+
expect(portalVnode2.el).toBe(container.childNodes[3])
235+
expect(portalVnode2.anchor).toBe(container.childNodes[4])
228236

237+
expect(portalVnode1.target).toBe(portalContainer)
229238
expect((portalVnode1 as any).children[0].el).toBe(
230239
portalContainer.childNodes[0]
231240
)
232-
expect(portalVnode1.anchor).toBe(portalContainer.childNodes[2])
241+
expect(portalVnode1.targetAnchor).toBe(portalContainer.childNodes[2])
242+
243+
expect(portalVnode2.target).toBe(portalContainer)
233244
expect((portalVnode2 as any).children[0].el).toBe(
234245
portalContainer.childNodes[3]
235246
)
236-
expect(portalVnode2.anchor).toBe(portalContainer.childNodes[5])
247+
expect(portalVnode2.targetAnchor).toBe(portalContainer.childNodes[5])
237248

238249
// // event handler
239250
triggerEvent('click', portalContainer.querySelector('.foo')!)
@@ -249,6 +260,68 @@ describe('SSR hydration', () => {
249260
)
250261
})
251262

263+
test('Portal (disabled)', async () => {
264+
const msg = ref('foo')
265+
const fn1 = jest.fn()
266+
const fn2 = jest.fn()
267+
268+
const Comp = () => [
269+
h('div', 'foo'),
270+
h(Portal, { target: '#portal3', disabled: true }, [
271+
h('span', msg.value),
272+
h('span', { class: msg.value, onClick: fn1 })
273+
]),
274+
h('div', { class: msg.value + '2', onClick: fn2 }, 'bar')
275+
]
276+
277+
const portalContainer = document.createElement('div')
278+
portalContainer.id = 'portal3'
279+
const ctx: SSRContext = {}
280+
const mainHtml = await renderToString(h(Comp), ctx)
281+
expect(mainHtml).toMatchInlineSnapshot(
282+
`"<!--[--><div>foo</div><!--portal start--><span>foo</span><span class=\\"foo\\"></span><!--portal end--><div class=\\"foo2\\">bar</div><!--]-->"`
283+
)
284+
285+
const portalHtml = ctx.portals!['#portal3']
286+
expect(portalHtml).toMatchInlineSnapshot(`"<!---->"`)
287+
288+
portalContainer.innerHTML = portalHtml
289+
document.body.appendChild(portalContainer)
290+
291+
const { vnode, container } = mountWithHydration(mainHtml, Comp)
292+
expect(vnode.el).toBe(container.firstChild)
293+
const children = vnode.children as VNode[]
294+
295+
expect(children[0].el).toBe(container.childNodes[1])
296+
297+
const portalVnode = children[1]
298+
expect(portalVnode.el).toBe(container.childNodes[2])
299+
expect((portalVnode.children as VNode[])[0].el).toBe(
300+
container.childNodes[3]
301+
)
302+
expect((portalVnode.children as VNode[])[1].el).toBe(
303+
container.childNodes[4]
304+
)
305+
expect(portalVnode.anchor).toBe(container.childNodes[5])
306+
expect(children[2].el).toBe(container.childNodes[6])
307+
308+
expect(portalVnode.target).toBe(portalContainer)
309+
expect(portalVnode.targetAnchor).toBe(portalContainer.childNodes[0])
310+
311+
// // event handler
312+
triggerEvent('click', container.querySelector('.foo')!)
313+
expect(fn1).toHaveBeenCalled()
314+
315+
triggerEvent('click', container.querySelector('.foo2')!)
316+
expect(fn2).toHaveBeenCalled()
317+
318+
msg.value = 'bar'
319+
await nextTick()
320+
expect(container.innerHTML).toMatchInlineSnapshot(
321+
`"<!--[--><div>foo</div><!--portal start--><span>bar</span><span class=\\"bar\\"></span><!--portal end--><div class=\\"bar2\\">bar</div><!--]-->"`
322+
)
323+
})
324+
252325
// compile SSR + client render fn from the same template & hydrate
253326
test('full compiler integration', async () => {
254327
const mounted: string[] = []

0 commit comments

Comments
 (0)