refactor: fix implementation of SFC :slotted id handling

fix #2892
This commit is contained in:
Evan You
2021-03-05 11:10:06 -05:00
parent cc975c1292
commit aea88c3280
36 changed files with 723 additions and 457 deletions

View File

@@ -2,13 +2,13 @@ import {
createApp,
h,
createCommentVNode,
withScopeId,
resolveComponent,
ComponentOptions,
ref,
defineComponent,
createTextVNode,
createStaticVNode
createStaticVNode,
withCtx
} from 'vue'
import { escapeHtml } from '@vue/shared'
import { renderToString } from '../src/renderToString'
@@ -634,34 +634,32 @@ function testRender(type: string, render: typeof renderToString) {
describe('scopeId', () => {
// note: here we are only testing scopeId handling for vdom serialization.
// compiled srr render functions will include scopeId directly in strings.
const withId = withScopeId('data-v-test')
const withChildId = withScopeId('data-v-child')
test('basic', async () => {
expect(
await render(
withId(() => {
return h('div')
})()
)
).toBe(`<div data-v-test></div>`)
const Foo = {
__scopeId: 'data-v-test',
render() {
return h('div')
}
}
expect(await render(h(Foo))).toBe(`<div data-v-test></div>`)
})
test('with slots', async () => {
const Child = {
__scopeId: 'data-v-child',
render: withChildId(function(this: any) {
render: function(this: any) {
return h('div', this.$slots.default())
})
}
}
const Parent = {
__scopeId: 'data-v-test',
render: withId(() => {
render: () => {
return h(Child, null, {
default: withId(() => h('span', 'slot'))
default: withCtx(() => h('span', 'slot'))
})
})
}
}
expect(await render(h(Parent))).toBe(

View File

@@ -1,11 +1,9 @@
import { createApp, withScopeId } from 'vue'
import { createApp, mergeProps, withCtx } from 'vue'
import { renderToString } from '../src/renderToString'
import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'
describe('ssr: scoped id on component root', () => {
test('basic', async () => {
const withParentId = withScopeId('parent')
describe('ssr: scopedId runtime behavior', () => {
test('id on component root', async () => {
const Child = {
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)}></div>`)
@@ -13,19 +11,19 @@ describe('ssr: scoped id on component root', () => {
}
const Comp = {
ssrRender: withParentId((ctx: any, push: any, parent: any) => {
__scopeId: 'parent',
ssrRender: (ctx: any, push: any, parent: any) => {
push(ssrRenderComponent(Child), null, null, parent)
})
}
}
const result = await renderToString(createApp(Comp))
expect(result).toBe(`<div parent></div>`)
})
test('inside slot', async () => {
const withParentId = withScopeId('parent')
test('id and :slotted on component root', async () => {
const Child = {
// <div></div>
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
}
@@ -34,29 +32,126 @@ describe('ssr: scoped id on component root', () => {
const Wrapper = {
__scopeId: 'wrapper',
ssrRender: (ctx: any, push: any, parent: any) => {
ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
// <slot/>
ssrRenderSlot(
ctx.$slots,
'default',
{},
null,
push,
parent,
'wrapper-s'
)
}
}
const Comp = {
ssrRender: withParentId((_: any, push: any, parent: any) => {
__scopeId: 'parent',
ssrRender: (_: any, push: any, parent: any) => {
// <Wrapper><Child/></Wrapper>
push(
ssrRenderComponent(
Wrapper,
null,
{
default: withParentId((_: any, push: any, parent: any) => {
push(ssrRenderComponent(Child, null, null, parent))
}),
default: withCtx(
(_: any, push: any, parent: any, scopeId: string) => {
push(ssrRenderComponent(Child, null, null, parent, scopeId))
}
),
_: 1
} as any,
parent
)
)
})
}
}
const result = await renderToString(createApp(Comp))
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
})
// #2892
test(':slotted on forwarded slots', async () => {
const Wrapper = {
__scopeId: 'wrapper',
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
// <div class="wrapper"><slot/></div>
push(
`<div${ssrRenderAttrs(
mergeProps({ class: 'wrapper' }, attrs)
)} wrapper>`
)
ssrRenderSlot(
ctx.$slots,
'default',
{},
null,
push,
parent,
'wrapper-s'
)
push(`</div>`)
}
}
const Slotted = {
__scopeId: 'slotted',
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
// <Wrapper><slot/></Wrapper>
push(
ssrRenderComponent(
Wrapper,
attrs,
{
default: withCtx(
(_: any, push: any, parent: any, scopeId: string) => {
ssrRenderSlot(
ctx.$slots,
'default',
{},
null,
push,
parent,
'slotted-s' + scopeId
)
}
),
_: 1
} as any,
parent
)
)
}
}
const Root = {
__scopeId: 'root',
// <Slotted><div></div></Slotted>
ssrRender: (_: any, push: any, parent: any, attrs: any) => {
push(
ssrRenderComponent(
Slotted,
attrs,
{
default: withCtx(
(_: any, push: any, parent: any, scopeId: string) => {
push(`<div root${scopeId}></div>`)
}
),
_: 1
} as any,
parent
)
)
}
}
const result = await renderToString(createApp(Root))
expect(result).toBe(
`<div class="wrapper" root slotted wrapper>` +
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
`</div>`
)
})
})