diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index c0e1b716f..d769c0f19 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -2,11 +2,16 @@ import { createComponent, + createSlot, createVaporApp, defineComponent, getCurrentInstance, + insert, nextTick, + prepend, ref, + renderEffect, + setText, template, } from '../src' import { makeRender } from './_utils' @@ -237,4 +242,190 @@ describe('component: slots', () => { 'Slot "default" invoked outside of the render function', ).not.toHaveBeenWarned() }) + + describe('createSlot', () => { + test('slot should be render correctly', () => { + const Comp = defineComponent(() => { + const n0 = template('
')() + insert(createSlot('header'), n0 as any as ParentNode) + return n0 + }) + + const { host } = define(() => { + return createComponent(Comp, {}, { header: () => template('header')() }) + }).render() + + expect(host.innerHTML).toBe('
header
') + }) + + test('slot should be render correctly with binds', async () => { + const Comp = defineComponent(() => { + const n0 = template('
')() + insert( + createSlot('header', { title: () => 'header' }), + n0 as any as ParentNode, + ) + return n0 + }) + + const { host } = define(() => { + return createComponent( + Comp, + {}, + { + header: ({ title }) => { + const el = template('

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

header

') + }) + + test('dynamic slot should be render correctly with binds', async () => { + const Comp = defineComponent(() => { + const n0 = template('
')() + prepend( + n0 as any as ParentNode, + createSlot('header', { title: () => 'header' }), + ) + return n0 + }) + + const { host } = define(() => { + // dynamic slot + return createComponent(Comp, {}, {}, () => [ + { name: 'header', fn: ({ title }) => template(`${title()}`)() }, + ]) + }).render() + + expect(host.innerHTML).toBe('
header
') + }) + + test('dynamic slot outlet should be render correctly with binds', async () => { + const Comp = defineComponent(() => { + const n0 = template('
')() + prepend( + n0 as any as ParentNode, + createSlot( + () => 'header', // dynamic slot outlet name + { title: () => 'header' }, + ), + ) + return n0 + }) + + const { host } = define(() => { + return createComponent( + Comp, + {}, + { header: ({ title }) => template(`${title()}`)() }, + ) + }).render() + + expect(host.innerHTML).toBe('
header
') + }) + + test('fallback should be render correctly', () => { + const Comp = defineComponent(() => { + const n0 = template('
')() + insert( + createSlot('header', {}, () => 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 = defineComponent(() => { + const temp0 = template('

') + const el0 = temp0() + const el1 = temp0() + const slot1 = createSlot('one', {}, () => template('one fallback')()) + const slot2 = createSlot('two', {}, () => 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, {}, {}, () => [ + 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 = defineComponent(() => { + const temp0 = template('

') + const el0 = temp0() + const slot1 = createSlot( + () => slotOutletName.value, + {}, + () => 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

') + }) + }) }) diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 48ea4509c..dc2da78ea 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,8 +1,23 @@ -import { type IfAny, isArray } from '@vue/shared' -import { baseWatch } from '@vue/reactivity' -import { type ComponentInternalInstance, setCurrentInstance } from './component' -import type { Block } from './apiRender' -import { createVaporPreScheduler } from './scheduler' +import { type IfAny, isArray, isFunction } from '@vue/shared' +import { + type EffectScope, + ReactiveEffect, + type SchedulerJob, + SchedulerJobFlags, + effectScope, + isReactive, + shallowReactive, +} from '@vue/reactivity' +import { + type ComponentInternalInstance, + currentInstance, + setCurrentInstance, +} from './component' +import { type Block, type Fragment, fragmentKey } from './apiRender' +import { renderEffect } from './renderEffect' +import { createComment, createTextNode, insert, remove } from './dom/element' +import { queueJob } from './scheduler' +import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' // TODO: SSR @@ -29,7 +44,7 @@ export const initSlots = ( rawSlots: InternalSlots | null = null, dynamicSlots: DynamicSlots | null = null, ) => { - const slots: InternalSlots = {} + let slots: InternalSlots = {} for (const key in rawSlots) { const slot = rawSlots[key] @@ -39,50 +54,56 @@ export const initSlots = ( } if (dynamicSlots) { + slots = shallowReactive(slots) const dynamicSlotKeys: Record = {} - baseWatch( - () => { - const _dynamicSlots = dynamicSlots() - for (let i = 0; i < _dynamicSlots.length; i++) { - const slot = _dynamicSlots[i] - // array of dynamic slot generated by