// NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`. import { child, createComponent, createFor, createForSlots, createIf, createSlot, createVaporApp, defineVaporComponent, insert, prepend, renderEffect, setInsertionState, template, txt, vaporInteropPlugin, withVaporCtx, } from '../src' import { type Ref, createApp, createSlots, currentInstance, h, nextTick, ref, renderSlot, toDisplayString, } from '@vue/runtime-dom' import { makeRender } from './_utils' import type { DynamicSlot } from '../src/componentSlots' import { setElementText, setText } from '../src/dom/prop' const define = makeRender() function renderWithSlots(slots: any): any { let instance: any const Comp = defineVaporComponent({ setup() { const t0 = template('
') const n0 = t0() instance = currentInstance return n0 }, }) const { render } = define({ render() { return createComponent(Comp, {}, slots) }, }) render() return instance } describe('component: slots', () => { test('initSlots: instance.slots should be set correctly', () => { const { slots } = renderWithSlots({ default: () => template('')(), }) expect(slots.default()).toMatchObject(document.createElement('span')) }) test('updateSlots: instance.slots should be updated correctly', async () => { const flag1 = ref(true) let instance: any const Child = () => { instance = currentInstance return template('child')() } const { render } = define({ render() { return createComponent( Child, {}, { $: [ () => flag1.value ? { name: 'one', fn: () => template('')() } : { name: 'two', fn: () => template('
')() }, ], }, ) }, }) render() expect(instance.slots).toHaveProperty('one') expect(instance.slots).not.toHaveProperty('two') flag1.value = false await nextTick() expect(instance.slots).not.toHaveProperty('one') expect(instance.slots).toHaveProperty('two') }) // passes but no warning for slot invocation in vapor currently test.todo('should not warn when mounting another app in setup', () => { const Comp = defineVaporComponent({ setup(_, { slots }) { return slots.default!() }, }) const mountComp = () => { createVaporApp({ render() { return createComponent( Comp, {}, { default: () => template('msg')() }, )! }, }) } const App = { setup() { mountComp() return [] }, } createVaporApp(App).mount(document.createElement('div')) expect( 'Slot "default" invoked outside of the render function', ).not.toHaveBeenWarned() }) describe('createSlot', () => { test('slot should be rendered correctly', () => { const Comp = defineVaporComponent(() => { const n0 = template('
')() insert(createSlot('header'), n0 as any as ParentNode) return n0 }) const { host } = define(() => { return createComponent(Comp, null, { header: () => template('header')(), }) }).render() expect(host.innerHTML).toBe('
header
') }) test('slot should be rendered correctly with slot props', async () => { const src = ref('header') const Comp = defineVaporComponent(() => { const n0 = template('
')() insert( createSlot('header', { title: () => src.value }), n0 as any as ParentNode, ) return n0 }) const { host } = define(() => { return createComponent(Comp, null, { header: props => { const el = template('

')() renderEffect(() => { setElementText(el, props.title) }) return el }, }) }).render() expect(host.innerHTML).toBe('

header

') src.value = 'footer' await nextTick() expect(host.innerHTML).toBe('

footer

') }) test('dynamic slot props', async () => { let props: any const bindObj = ref>({ foo: 1, baz: 'qux' }) const Comp = defineVaporComponent(() => createSlot('default', { $: [() => bindObj.value] }), ) define(() => createComponent(Comp, null, { default: (_props: any) => ((props = _props), []), }), ).render() expect(props).toEqual({ foo: 1, baz: 'qux' }) bindObj.value.foo = 2 await nextTick() expect(props).toEqual({ foo: 2, baz: 'qux' }) delete bindObj.value.baz await nextTick() expect(props).toEqual({ foo: 2 }) }) test('dynamic slot props with static slot props', async () => { let props: any const foo = ref(0) const bindObj = ref>({ foo: 100, baz: 'qux' }) const Comp = defineVaporComponent(() => createSlot('default', { foo: () => foo.value, $: [() => bindObj.value], }), ) define(() => createComponent(Comp, null, { default: (_props: any) => ((props = _props), []), }), ).render() expect(props).toEqual({ foo: 100, baz: 'qux' }) foo.value = 2 await nextTick() expect(props).toEqual({ foo: 100, baz: 'qux' }) delete bindObj.value.foo await nextTick() expect(props).toEqual({ foo: 2, baz: 'qux' }) }) test('dynamic slot should be rendered correctly with slot props', async () => { const val = ref('header') const Comp = defineVaporComponent(() => { const n0 = template('
')() prepend( n0 as any as ParentNode, createSlot('header', { title: () => val.value }), ) return n0 }) const { host } = define(() => { // dynamic slot return createComponent(Comp, null, { $: [ () => ({ name: 'header', fn: (props: any) => { const el = template('

')() renderEffect(() => { setElementText(el, props.title) }) return el }, }), ], }) }).render() expect(host.innerHTML).toBe('

header

') val.value = 'footer' await nextTick() expect(host.innerHTML).toBe('

footer

') }) test('dynamic slot outlet should be render correctly with slot props', async () => { const val = ref('header') const Comp = defineVaporComponent(() => { const n0 = template('
')() prepend( n0 as any as ParentNode, createSlot( () => val.value, // dynamic slot outlet name ), ) return n0 }) const { host } = define(() => { return createComponent(Comp, null, { header: () => template('header')(), footer: () => template('footer')(), }) }).render() expect(host.innerHTML).toBe('
header
') val.value = 'footer' await nextTick() expect(host.innerHTML).toBe('
footer
') }) test('fallback should be render correctly', () => { const Comp = defineVaporComponent(() => { const n0 = template('
')() insert( createSlot('header', undefined, () => template('fallback')()), n0 as any as ParentNode, ) return n0 }) const { host } = define(() => { return createComponent(Comp, {}, {}) }).render() expect(host.innerHTML).toBe('
fallback
') }) test('dynamic slot should be updated correctly', async () => { const flag1 = ref(true) const Child = defineVaporComponent(() => { const temp0 = template('

') const el0 = temp0() const el1 = temp0() const slot1 = createSlot('one', null, () => template('one fallback')()) const slot2 = createSlot('two', null, () => template('two fallback')()) insert(slot1, el0 as any as ParentNode) insert(slot2, el1 as any as ParentNode) return [el0, el1] }) const { host } = define(() => { return createComponent(Child, null, { $: [ () => flag1.value ? { name: 'one', fn: () => template('one content')(), } : { name: 'two', fn: () => template('two content')(), }, ], }) }).render() expect(host.innerHTML).toBe( '

one content

two fallback

', ) flag1.value = false await nextTick() expect(host.innerHTML).toBe( '

one fallback

two content

', ) flag1.value = true await nextTick() expect(host.innerHTML).toBe( '

one content

two fallback

', ) }) test('dynamic slot outlet should be updated correctly', async () => { const slotOutletName = ref('one') const Child = defineVaporComponent(() => { const temp0 = template('

') const el0 = temp0() const slot1 = createSlot( () => slotOutletName.value, undefined, () => template('fallback')(), ) insert(slot1, el0 as any as ParentNode) return el0 }) const { host } = define(() => { return createComponent( Child, {}, { one: () => template('one content')(), two: () => template('two content')(), }, ) }).render() expect(host.innerHTML).toBe('

one content

') slotOutletName.value = 'two' await nextTick() expect(host.innerHTML).toBe('

two content

') slotOutletName.value = 'none' await nextTick() expect(host.innerHTML).toBe('

fallback

') }) test('non-exist slot', async () => { const Child = defineVaporComponent(() => { const el0 = template('

')() const slot = createSlot('not-exist', undefined) insert(slot, el0 as any as ParentNode) return el0 }) const { host } = define(() => { return createComponent(Child) }).render() expect(host.innerHTML).toBe('

') }) test('use fallback when inner content changes', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const toggle = ref(true) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) }, }) }, }).render() expect(html()).toBe('content') toggle.value = false await nextTick() expect(html()).toBe('fallback') toggle.value = true await nextTick() expect(html()).toBe('content') }) test('use fallback on initial render', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const toggle = ref(false) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) }, }) }, }).render() expect(html()).toBe('fallback') toggle.value = true await nextTick() expect(html()).toBe('content') toggle.value = false await nextTick() expect(html()).toBe('fallback') }) test('dynamic slot work with v-if', async () => { const val = ref('header') const toggle = ref(false) const Comp = defineVaporComponent(() => { const n0 = template('
')() prepend(n0 as any as ParentNode, createSlot('header', null)) return n0 }) const { host } = define(() => { // dynamic slot return createComponent(Comp, null, { $: [ () => (toggle.value ? { name: val.value, fn: () => { return template('

')() }, } : void 0) as DynamicSlot, ], }) }).render() expect(host.innerHTML).toBe('
') toggle.value = true await nextTick() expect(host.innerHTML).toBe('

') }) test('slots proxy ownKeys trap correctly reflects dynamic slot presence', async () => { const val = ref('header') const toggle = ref(false) let instance: any const Comp = defineVaporComponent(() => { instance = currentInstance const n0 = template('
')() prepend(n0 as any as ParentNode, createSlot('header', null)) return n0 }) define(() => { // dynamic slot return createComponent(Comp, null, { $: [ () => (toggle.value ? { name: val.value, fn: () => { return template('

')() }, } : void 0) as DynamicSlot, ], }) }).render() expect(Reflect.ownKeys(instance.slots)).not.toContain('header') toggle.value = true await nextTick() expect(Reflect.ownKeys(instance.slots)).toContain('header') toggle.value = false await nextTick() expect(Reflect.ownKeys(instance.slots)).not.toContain('header') }) test('render fallback when slot content is not valid', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const { html } = define({ setup() { return createComponent(Child, null, { default: () => { return template('')() }, }) }, }).render() expect(html()).toBe('fallback') }) test('render fallback when v-if condition is false', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const toggle = ref(false) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { return createIf( () => toggle.value, () => { return document.createTextNode('content') }, ) }, }) }, }).render() expect(html()).toBe('fallback') toggle.value = true await nextTick() expect(html()).toBe('content') toggle.value = false await nextTick() expect(html()).toBe('fallback') }) test('render fallback with nested v-if', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const outerShow = ref(false) const innerShow = ref(false) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { return createIf( () => outerShow.value, () => { return createIf( () => innerShow.value, () => { return document.createTextNode('content') }, ) }, ) }, }) }, }).render() expect(html()).toBe('fallback') outerShow.value = true await nextTick() expect(html()).toBe('fallback') innerShow.value = true await nextTick() expect(html()).toBe('content') innerShow.value = false await nextTick() expect(html()).toBe('fallback') outerShow.value = false await nextTick() expect(html()).toBe('fallback') outerShow.value = true await nextTick() expect(html()).toBe('fallback') innerShow.value = true await nextTick() expect(html()).toBe('content') }) test('render fallback with v-for', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const items = ref([1]) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { const n2 = createFor( () => items.value, for_item0 => { const n4 = template(' ')() as any const x4 = child(n4) as any renderEffect(() => setText(x4, toDisplayString(for_item0.value)), ) return n4 }, ) return n2 }, }) }, }).render() expect(html()).toBe('1') items.value.pop() await nextTick() expect(html()).toBe('fallback') items.value.pop() await nextTick() expect(html()).toBe('fallback') items.value.push(2) await nextTick() expect(html()).toBe('2') }) test('render fallback with v-for (empty source)', async () => { const Child = { setup() { return createSlot('default', null, () => document.createTextNode('fallback'), ) }, } const items = ref([]) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { const n2 = createFor( () => items.value, for_item0 => { const n4 = template(' ')() as any const x4 = child(n4) as any renderEffect(() => setText(x4, toDisplayString(for_item0.value)), ) return n4 }, ) return n2 }, }) }, }).render() expect(html()).toBe('fallback') items.value.push(1) await nextTick() expect(html()).toBe('1') items.value.pop() await nextTick() expect(html()).toBe('fallback') items.value.pop() await nextTick() expect(html()).toBe('fallback') items.value.push(2) await nextTick() expect(html()).toBe('2') }) test('work with v-once', async () => { const Child = defineVaporComponent({ setup() { return createSlot( 'default', null, undefined, undefined, true /* once */, ) }, }) const count = ref(0) const { html } = define({ setup() { return createComponent(Child, null, { default: () => { const n3 = template('
')() as any const x3 = txt(n3) as any renderEffect(() => setText(x3, toDisplayString(count.value))) return n3 }, }) }, }).render() expect(html()).toBe('
0
') // expect no changes due to v-once count.value++ await nextTick() expect(html()).toBe('
0
') }) }) describe('forwarded slot', () => { test('should work', async () => { const Child = defineVaporComponent({ setup() { return createSlot('foo', null) }, }) const Parent = defineVaporComponent({ setup() { const n2 = createComponent( Child, null, { foo: withVaporCtx(() => { return createSlot('foo', null) }), }, true, ) return n2 }, }) const foo = ref('foo') const { host } = define({ setup() { const n2 = createComponent( Parent, null, { foo: () => { const n0 = template(' ')() as any renderEffect(() => setText(n0, foo.value)) return n0 }, }, true, ) return n2 }, }).render() expect(host.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(host.innerHTML).toBe('bar') }) test('mixed with non-forwarded slot', async () => { const Child = defineVaporComponent({ setup() { return [createSlot('foo', null)] }, }) const Parent = defineVaporComponent({ setup() { const n2 = createComponent(Child, null, { foo: withVaporCtx(() => { const n0 = createSlot('foo', null) return n0 }), }) const n3 = createSlot('default', null) return [n2, n3] }, }) const foo = ref('foo') const { host } = define({ setup() { const n2 = createComponent( Parent, null, { foo: () => { const n0 = template(' ')() as any renderEffect(() => setText(n0, foo.value)) return n0 }, default: () => { const n3 = template(' ')() as any renderEffect(() => setText(n3, foo.value)) return n3 }, }, true, ) return n2 }, }).render() expect(host.innerHTML).toBe('foofoo') foo.value = 'bar' await nextTick() expect(host.innerHTML).toBe('barbar') }) test('forwarded slot with fallback', async () => { const Child = defineVaporComponent({ setup() { return createSlot('default', null, () => template('child fallback')()) }, }) const Parent = defineVaporComponent({ setup() { const n2 = createComponent(Child, null, { default: withVaporCtx(() => { const n0 = createSlot('default', null, () => { return template('')() }) return n0 }), }) return n2 }, }) const { html } = define({ setup() { return createComponent(Parent, null, { default: () => template('')(), }) }, }).render() expect(html()).toBe('child fallback') }) test('named forwarded slot with v-if', async () => { const Child = defineVaporComponent({ setup() { return createSlot('default', null) }, }) const Parent = defineVaporComponent({ props: { show: Boolean, }, setup(props) { const n6 = createComponent( Child, null, { default: withVaporCtx(() => { const n0 = createIf( () => props.show, () => { const n5 = template('
')() as any setInsertionState(n5, null, 0, true) createSlot('header', null, () => { const n4 = template('default header')() return n4 }) return n5 }, ) return n0 }), }, true, ) return n6 }, }) const show = ref(false) const { html } = define({ setup() { return createComponent( Parent, { show: () => show.value, }, { header: () => template('custom header')(), }, ) }, }).render() expect(html()).toBe('') show.value = true await nextTick() expect(html()).toBe( '
custom header
', ) show.value = false await nextTick() expect(html()).toBe('') }) test('forwarded slot with fallback (v-if)', async () => { const Child = defineVaporComponent({ setup() { return createSlot('default', null, () => template('child fallback')()) }, }) const show = ref(false) const Parent = defineVaporComponent({ setup() { const n2 = createComponent(Child, null, { default: withVaporCtx(() => { const n0 = createSlot('default', null, () => { const n2 = createIf( () => show.value, () => { const n4 = template('
if content
')() return n4 }, ) return n2 }) return n0 }), }) return n2 }, }) const { html } = define({ setup() { return createComponent(Parent, null, { default: () => template('')(), }) }, }).render() expect(html()).toBe('child fallback') show.value = true await nextTick() expect(html()).toBe( '
if content
', ) }) test('forwarded slot with fallback (v-for)', async () => { const Child = defineVaporComponent({ setup() { return createSlot('default', null, () => template('child fallback')()) }, }) const items = ref([]) const Parent = defineVaporComponent({ setup() { const n2 = createComponent(Child, null, { default: withVaporCtx(() => { const n0 = createSlot('default', null, () => { const n2 = createFor( () => items.value, for_item0 => { const n4 = template(' ')() as any const x4 = child(n4) as any renderEffect(() => setText(x4, toDisplayString(for_item0.value)), ) return n4 }, ) return n2 }) return n0 }), }) return n2 }, }) const { html } = define({ setup() { return createComponent(Parent, null, { default: () => template('')(), }) }, }).render() expect(html()).toBe('child fallback') items.value.push(1) await nextTick() expect(html()).toBe('1') items.value.pop() await nextTick() expect(html()).toBe('child fallback') }) test('consecutive slots with insertion state', async () => { const { component: Child } = define({ setup() { const n2 = template('
baz
', true)() as any setInsertionState(n2, 0) createSlot('default', null) setInsertionState(n2, 0) createSlot('foo', null) return n2 }, }) const { html } = define({ setup() { return createComponent(Child, null, { default: () => template('default')(), foo: () => template('foo')(), }) }, }).render() expect(html()).toBe( `
` + `default` + `foo` + `
baz
` + `
`, ) }) describe('vdom interop', () => { const createVaporSlot = (fallbackText = 'fallback') => { return defineVaporComponent({ setup() { const n0 = createSlot('foo', null, () => { const n2 = template(`
${fallbackText}
`)() return n2 }) return n0 }, }) } const createVdomSlot = (fallbackText = 'fallback') => { return { render(this: any) { return renderSlot(this.$slots, 'foo', {}, () => [ h('div', fallbackText), ]) }, } } const createVaporForwardedSlot = ( targetComponent: any, fallbackText?: string, ) => { return defineVaporComponent({ setup() { const n2 = createComponent( targetComponent, null, { foo: withVaporCtx(() => { return fallbackText ? createSlot('foo', null, () => { const n2 = template(`
${fallbackText}
`)() return n2 }) : createSlot('foo', null) }), }, true, ) return n2 }, }) } const createVdomForwardedSlot = ( targetComponent: any, fallbackText?: string, ) => { return { render(this: any) { return h(targetComponent, null, { foo: () => [ fallbackText ? renderSlot(this.$slots, 'foo', {}, () => [ h('div', fallbackText), ]) : renderSlot(this.$slots, 'foo'), ], _: 3 /* FORWARDED */, }) }, } } const createMultipleVaporForwardedSlots = ( targetComponent: any, count: number, ) => { let current = targetComponent for (let i = 0; i < count; i++) { current = createVaporForwardedSlot(current) } return current } const createMultipleVdomForwardedSlots = ( targetComponent: any, count: number, ) => { let current = targetComponent for (let i = 0; i < count; i++) { current = createVdomForwardedSlot(current) } return current } const createTestApp = ( rootComponent: any, foo: Ref, show: Ref, ) => { return { setup() { return () => h( rootComponent, null, createSlots({ _: 2 /* DYNAMIC */ } as any, [ show.value ? { name: 'foo', fn: () => [h('span', foo.value)], key: '0', } : undefined, ]), ) }, } } const createEmptyTestApp = (rootComponent: any) => { return { setup() { return () => h(rootComponent) }, } } test('vdom slot > vapor forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlotWithFallback = createVaporForwardedSlot( VaporSlot, 'forwarded fallback', ) const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
forwarded fallback
') }) test('vdom slot > vapor forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlot = createVaporForwardedSlot(VdomSlot) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlotWithFallback = createVaporForwardedSlot( VdomSlot, 'forwarded fallback', ) const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
forwarded fallback
') }) test('vdom slot > vapor forwarded slot > vdom forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) const VaporForwardedSlotWithFallback = createVaporForwardedSlot( VdomForwardedSlot, 'forwarded fallback', ) const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
forwarded fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlotWithFallback = createVdomForwardedSlot( VaporSlot, 'vdom fallback', ) const VaporForwardedSlot = createVaporForwardedSlot( VdomForwardedSlotWithFallback, ) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vdom fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot(empty) > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => { const VaporSlot = createVaporSlot() const VdomForwardedSlotWithFallback = createVdomForwardedSlot( VaporSlot, 'vdom fallback', ) const VaporForwardedSlot = createVaporForwardedSlot( VdomForwardedSlotWithFallback, ) const App = createEmptyTestApp(VaporForwardedSlot) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('
vdom fallback
') }) test('vdom slot > vapor forwarded slot > vdom forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) const VaporForwardedSlotWithFallback = createVaporForwardedSlot( VdomForwardedSlot, 'vapor fallback', ) const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vapor fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlotWithFallback = createVdomForwardedSlot( VdomSlot, 'vdom fallback', ) const VaporForwardedSlot = createVaporForwardedSlot( VdomForwardedSlotWithFallback, ) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vdom fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) const VaporForwardedSlot = createMultipleVaporForwardedSlots( VdomForwardedSlot, 3, ) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlotWithFallback = createVdomForwardedSlot( VdomSlot, 'vdom fallback', ) const VaporForwardedSlot = createMultipleVaporForwardedSlots( VdomForwardedSlotWithFallback, 3, ) const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe( '
vdom fallback
', ) show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot > vapor forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) const VdomForwardedSlot = createVdomForwardedSlot(VaporForwardedSlot) const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) const VdomForwardedSlot = createMultipleVdomForwardedSlots( VaporForwardedSlot, 3, ) const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot = createVaporForwardedSlot( VaporSlot, 'vapor fallback', ) const VdomForwardedSlot = createMultipleVdomForwardedSlots( VaporForwardedSlot, 3, ) const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vapor fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlot1 = createMultipleVaporForwardedSlots( VdomSlot, 2, ) const App = createTestApp(VaporForwardedSlot1, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlot2 = createVaporForwardedSlot(VdomSlot) const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( VaporForwardedSlot2, 'vapor1 fallback', ) const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vapor1 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( VdomSlot, 'vapor2 fallback', ) const VaporForwardedSlot1 = createVaporForwardedSlot( VaporForwardedSlot2WithFallback, ) const App = createTestApp(VaporForwardedSlot1, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vapor2 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot2 = createVaporForwardedSlot(VaporSlot) const VaporForwardedSlot1 = createVaporForwardedSlot(VaporForwardedSlot2) const App = createTestApp(VaporForwardedSlot1, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( VdomSlot, 'vapor2 fallback', ) const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( VaporForwardedSlot2WithFallback, 'vapor1 fallback', ) const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vapor1 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( VaporSlot, 'vapor2 fallback', ) const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( VaporForwardedSlot2WithFallback, 'vapor1 fallback', ) const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe( '
vapor1 fallback
', ) show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( VaporSlot, 'vdom2 fallback', ) const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( VdomForwardedSlot2WithFallback, 'vdom1 fallback', ) const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vdom1 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vdom slot', async () => { const foo = ref('foo') const show = ref(true) const VdomSlot = createVdomSlot() const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( VdomSlot, 'vdom2 fallback', ) const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( VdomForwardedSlot2WithFallback, 'vdom1 fallback', ) const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vdom1 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) (multiple) > vapor slot', async () => { const foo = ref('foo') const show = ref(true) const VaporSlot = createVaporSlot() const VdomForwardedSlot3WithFallback = createVdomForwardedSlot( VaporSlot, 'vdom3 fallback', ) const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( VdomForwardedSlot3WithFallback, 'vdom2 fallback', ) const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( VdomForwardedSlot2WithFallback, 'vdom1 fallback', ) const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') foo.value = 'bar' await nextTick() expect(root.innerHTML).toBe('bar') show.value = false await nextTick() expect(root.innerHTML).toBe('
vdom1 fallback
') show.value = true await nextTick() expect(root.innerHTML).toBe('bar') }) }) }) describe('createForSlots', () => { test('should work', async () => { const loop = ref([1, 2, 3]) let instance: any const Child = () => { instance = currentInstance return template('child')() } const { render } = define({ setup() { return createComponent(Child, null, { $: [ () => createForSlots(loop.value, (item, i) => ({ name: item, fn: () => template(item + i)(), })), ], }) }, }) render() expect(instance.slots).toHaveProperty('1') expect(instance.slots).toHaveProperty('2') expect(instance.slots).toHaveProperty('3') loop.value.push(4) await nextTick() expect(instance.slots).toHaveProperty('4') loop.value.shift() await nextTick() expect(instance.slots).not.toHaveProperty('1') }) test('should cache dynamic slot source result', async () => { const items = ref([1, 2, 3]) let callCount = 0 const getItems = () => { callCount++ return items.value } let instance: any const Child = defineVaporComponent(() => { instance = currentInstance // Create multiple slots to trigger multiple getSlot calls const n1 = template('
')() const n2 = template('
')() const n3 = template('
')() insert(createSlot('slot1'), n1 as any as ParentNode) insert(createSlot('slot2'), n2 as any as ParentNode) insert(createSlot('slot3'), n3 as any as ParentNode) return [n1, n2, n3] }) define({ setup() { return createComponent(Child, null, { $: [ () => createForSlots(getItems(), (item, i) => ({ name: 'slot' + item, fn: () => template(String(item))(), })), ], }) }, }).render() // getItems should only be called once expect(callCount).toBe(1) expect(instance.slots).toHaveProperty('slot1') expect(instance.slots).toHaveProperty('slot2') expect(instance.slots).toHaveProperty('slot3') }) test('should update when source changes', async () => { const items = ref([1, 2]) let callCount = 0 const getItems = () => { callCount++ return items.value } let instance: any const Child = defineVaporComponent(() => { instance = currentInstance const n1 = template('
')() const n2 = template('
')() const n3 = template('
')() insert(createSlot('slot1'), n1 as any as ParentNode) insert(createSlot('slot2'), n2 as any as ParentNode) insert(createSlot('slot3'), n3 as any as ParentNode) return [n1, n2, n3] }) define({ setup() { return createComponent(Child, null, { $: [ () => createForSlots(getItems(), (item, i) => ({ name: 'slot' + item, fn: () => template(String(item))(), })), ], }) }, }).render() expect(callCount).toBe(1) expect(instance.slots).toHaveProperty('slot1') expect(instance.slots).toHaveProperty('slot2') expect(instance.slots).not.toHaveProperty('slot3') // Update items items.value.push(3) await nextTick() // Should be called again after source changes expect(callCount).toBe(2) expect(instance.slots).toHaveProperty('slot1') expect(instance.slots).toHaveProperty('slot2') expect(instance.slots).toHaveProperty('slot3') }) test('should render slots correctly with caching', async () => { const items = ref([1, 2, 3, 4, 5]) const Child = defineVaporComponent(() => { const containers: any[] = [] for (let i = 1; i <= 5; i++) { const n = template('
')() insert(createSlot('slot' + i), n as any as ParentNode) containers.push(n) } return containers }) const { host } = define({ setup() { return createComponent(Child, null, { $: [ () => createForSlots(items.value, item => ({ name: 'slot' + item, fn: () => template('content' + item)(), })), ], }) }, }).render() expect(host.innerHTML).toBe( '
content1
' + '
content2
' + '
content3
' + '
content4
' + '
content5
', ) // Update items items.value = [2, 4] await nextTick() expect(host.innerHTML).toBe( '
' + '
content2
' + '
' + '
content4
' + '
', ) }) test('should work with null and undefined', async () => { const loop = ref(undefined) let instance: any const Child = () => { instance = currentInstance return template('child')() } const { render } = define({ setup() { return createComponent(Child, null, { $: [ () => createForSlots(loop.value as any, (item, i) => ({ name: item, fn: () => template(item + i)(), })), ], }) }, }) render() expect(instance.slots).toEqual({}) loop.value = [1] await nextTick() expect(instance.slots).toHaveProperty('1') loop.value = null await nextTick() expect(instance.slots).toEqual({}) }) }) })