import { Fragment, Suspense, createBlock, createCommentVNode, createVNode, defineComponent, h, nextTick, nodeOps, openBlock, popScopeId, pushScopeId, ref, render, renderSlot, serializeInner, withScopeId, } from '@vue/runtime-test' import { withCtx } from '../src/componentRenderContext' import { PatchFlags } from '@vue/shared' describe('scopeId runtime support', () => { test('should attach scopeId', () => { const App = { __scopeId: 'parent', render: () => h('div', [h('div')]), } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe(`
`) }) test('should attach scopeId to components in parent component', () => { const Child = { __scopeId: 'child', render: () => h('div'), } const App = { __scopeId: 'parent', render: () => h('div', [h(Child)]), } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe( `
`, ) }) // #5148 test('should attach scopeId to suspense content', async () => { const deps: Promise[] = [] const Child = { async setup() { const p = new Promise(r => setTimeout(r, 1)) deps.push(p.then(() => Promise.resolve())) await p return () => h('div', 'async') }, } const Wrapper = { setup(_: any, { slots }: any) { return () => slots.default({ Component: h(Child) }) }, } const App = { __scopeId: 'parent', setup() { return () => h(Wrapper, null, { default: withCtx(({ Component }: any) => h(Suspense, null, { default: h(Component), fallback: h('div', 'fallback'), }), ), }) }, } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe(`
fallback
`) await Promise.all(deps) await nextTick() expect(serializeInner(root)).toBe(`
async
`) }) // :slotted basic test('should work on slots', () => { const Child = { __scopeId: 'child', render(this: any) { return h('div', renderSlot(this.$slots, 'default')) }, } const Child2 = { __scopeId: 'child2', render: () => h('span'), } const App = { __scopeId: 'parent', render: () => { return h( Child, withCtx(() => { return [h('div'), h(Child2)] }), ) }, } const root = nodeOps.createElement('div') render(h(App), root) // slot content should have: // - scopeId from parent // - slotted scopeId (with `-s` postfix) from child (the tree owner) expect(serializeInner(root)).toBe( `
` + `
` + // component inside slot should have: // - scopeId from template context // - slotted scopeId from slot owner // - its own scopeId `` + `
`, ) }) // #2892 test(':slotted on forwarded slots', async () => { const Wrapper = { __scopeId: 'wrapper', render(this: any) { //
return h('div', { class: 'wrapper' }, [ renderSlot( this.$slots, 'default', {}, undefined, true /* noSlotted */, ), ]) }, } const Slotted = { __scopeId: 'slotted', render(this: any) { // return h(Wrapper, null, { default: withCtx(() => [renderSlot(this.$slots, 'default')]), }) }, } // simulate hoisted node pushScopeId('root') const hoisted = h('div', 'hoisted') popScopeId() const Root = { __scopeId: 'root', render(this: any) { //
hoisted
{{ dynamic }}
return h(Slotted, null, { default: withCtx(() => { return [hoisted, h('div', 'dynamic')] }), }) }, } const root = nodeOps.createElement('div') render(h(Root), root) expect(serializeInner(root)).toBe( `
` + `
hoisted
` + `
dynamic
` + `
`, ) const Root2 = { __scopeId: 'root', render(this: any) { // // //
hoisted
{{ dynamic }}
//
//
return h(Slotted, null, { default: withCtx(() => [ h(Wrapper, null, { default: withCtx(() => [hoisted, h('div', 'dynamic')]), }), ]), }) }, } const root2 = nodeOps.createElement('div') render(h(Root2), root2) expect(serializeInner(root2)).toBe( `
` + `
` + `
hoisted
` + `
dynamic
` + `
` + `
`, ) }) // #1988 test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => { const App = { __scopeId: 'parent', render: () => { return h(Child) }, } function Child() { return h(Child2, { class: 'foo' }) } function Child2() { return h('div') } Child2.inheritAttrs = false const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe(`
`) }) test('should inherit scopeId through nested DEV_ROOT_FRAGMENT with inheritAttrs: false', async () => { const Parent = { __scopeId: 'parent', render() { return h(Child, { class: 'foo' }) }, } const ok = ref(true) const Child = defineComponent({ inheritAttrs: false, render() { return ( openBlock(), createBlock( Fragment, null, [ createCommentVNode('comment1'), ok.value ? (openBlock(), createBlock('div', { key: 0 }, 'div1')) : (openBlock(), createBlock( Fragment, { key: 1 }, [ createCommentVNode('comment2'), createVNode('div', null, 'div2'), ], PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT, )), ], PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT, ) ) }, }) const root = nodeOps.createElement('div') render(h(Parent), root) expect(serializeInner(root)).toBe(`
div1
`) ok.value = false await nextTick() expect(serializeInner(root)).toBe( `
div2
`, ) }) }) describe('backwards compat with <=3.0.7', () => { const withParentId = withScopeId('parent') const withChildId = withScopeId('child') test('should attach scopeId', () => { const App = { __scopeId: 'parent', render: withParentId(() => { return h('div', [h('div')]) }), } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe(`
`) }) test('should attach scopeId to components in parent component', () => { const Child = { __scopeId: 'child', render: withChildId(() => { return h('div') }), } const App = { __scopeId: 'parent', render: withParentId(() => { return h('div', [h(Child)]) }), } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe( `
`, ) }) test('should work on slots', () => { const Child = { __scopeId: 'child', render: withChildId(function (this: any) { return h('div', renderSlot(this.$slots, 'default')) }), } const withChild2Id = withScopeId('child2') const Child2 = { __scopeId: 'child2', render: withChild2Id(() => h('span')), } const App = { __scopeId: 'parent', render: withParentId(() => { return h( Child, withParentId(() => { return [h('div'), h(Child2)] }), ) }), } const root = nodeOps.createElement('div') render(h(App), root) // slot content should have: // - scopeId from parent // - slotted scopeId (with `-s` postfix) from child (the tree owner) expect(serializeInner(root)).toBe( `
` + `
` + // component inside slot should have: // - scopeId from template context // - slotted scopeId from slot owner // - its own scopeId `` + `
`, ) }) // #1988 test('should inherit scopeId through nested HOCs with inheritAttrs: false', () => { const withParentId = withScopeId('parent') const App = { __scopeId: 'parent', render: withParentId(() => { return h(Child) }), } function Child() { return h(Child2, { class: 'foo' }) } function Child2() { return h('div') } Child2.inheritAttrs = false const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe(`
`) }) test('hoisted nodes', async () => { pushScopeId('foobar') const hoisted = h('div', 'hello') popScopeId() const App = { __scopeId: 'foobar', render: () => h('div', [hoisted]), } const root = nodeOps.createElement('div') render(h(App), root) expect(serializeInner(root)).toBe( `
hello
`, ) }) })