Skip to content

Commit

Permalink
fix(runtime-vapor): fix loop slots cleanup and add some annotate
Browse files Browse the repository at this point in the history
  • Loading branch information
Doctor-wu authored and sxzz committed Nov 13, 2024
1 parent 8a55a0d commit ec13986
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
70 changes: 70 additions & 0 deletions packages/runtime-vapor/__tests__/componentSlots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,76 @@ describe('component: slots', () => {
)
})

test('should cleanup all slots when loop slot has same key', async () => {
const loop = ref([1, 1, 1])

let childInstance
const t0 = template('<div></div>')
const { component: Child } = define({
setup() {
childInstance = getCurrentInstance()
const slots = useSlots()
const keys = () => Object.keys(slots)
return {
keys,
slots,
}
},
render: (_ctx: any) => {
const n0 = createFor(
() => _ctx.keys(),
(_ctx0: any) => {
const n5 = t0()
const n4 = createSlot(() => _ctx0[0])
insert(n4, n5 as ParentNode)
return n5
},
)
return n0
},
})

const t1 = template(' static default ')
const { render } = define({
setup() {
return createComponent(Child, {}, [
{
default: () => {
return t1()
},
},
() =>
createForSlots(loop.value, (item, i) => ({
name: item,
fn: () => template(item)(),
})),
])
},
})
const { html } = render()
expect(childInstance!.slots).toHaveProperty('1')
expect(childInstance!.slots).toHaveProperty('default')
expect(html()).toBe(

Check failure on line 444 in packages/runtime-vapor/__tests__/componentSlots.spec.ts

View workflow job for this annotation

GitHub Actions / test / unit-test

packages/runtime-vapor/__tests__/componentSlots.spec.ts > component: slots > should cleanup all slots when loop slot has same key

AssertionError: expected '<div><!--slot--></div><div><!--slot--…' to be '<div>1<!--slot--></div><div> static d…' // Object.is equality Expected: "<div>1<!--slot--></div><div> static default <!--slot--></div><!--for-->" Received: "<div><!--slot--></div><div><!--slot--></div><!--for-->" ❯ packages/runtime-vapor/__tests__/componentSlots.spec.ts:444:20
'<div>1<!--slot--></div><div> static default <!--slot--></div><!--for-->',
)
loop.value = [1]
await nextTick()
expect(childInstance!.slots).toHaveProperty('1')
expect(childInstance!.slots).toHaveProperty('default')
expect(html()).toBe(
'<div>1<!--slot--></div><div> static default <!--slot--></div><!--for-->',
)
loop.value = [1, 2, 3]
await nextTick()
expect(childInstance!.slots).toHaveProperty('1')
expect(childInstance!.slots).toHaveProperty('2')
expect(childInstance!.slots).toHaveProperty('3')
expect(childInstance!.slots).toHaveProperty('default')
expect(html()).toBe(
'<div>1<!--slot--></div><div>2<!--slot--></div><div>3<!--slot--></div><div> static default <!--slot--></div><!--for-->',
)
})

test('dynamicSlots should not cover high level slots', async () => {
const dynamicFlag = ref(true)

Expand Down
14 changes: 13 additions & 1 deletion packages/runtime-vapor/src/componentSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ export function initSlots(

instance.slots = shallowReactive({})
const renderedSlotKeys: Set<string>[] = []
/**
* Maintain a queue for each slot name, so that we can
* render the next slot when the highest level slot was removed
*/
const slotsQueue: Record<string, [level: number, slot: Slot][]> = {}
rawSlots.forEach((slots, index) => {
const isDynamicSlot = isDynamicSlotFn(slots)
if (isDynamicSlot) {
firstEffect(instance, () => {
const renderedKeys = (renderedSlotKeys[index] ||= new Set())
let dynamicSlot = slots()
// cleanup slots and re-calc to avoid diffing slots between renders
// cleanup will return a slotNames array contains the slot names that need to be restored
const restoreSlotNames = cleanupSlot(index)
if (isArray(dynamicSlot)) {
for (const slot of dynamicSlot) {
Expand All @@ -67,6 +73,7 @@ export function initSlots(
} else if (dynamicSlot) {
registerSlot(dynamicSlot.name, dynamicSlot.fn, index, renderedKeys)
}
// restore after re-calc slots
if (restoreSlotNames.length) {
for (const key of restoreSlotNames) {
const [restoreLevel, restoreFn] = slotsQueue[key][0]
Expand All @@ -75,6 +82,7 @@ export function initSlots(
addSlot(key, restoreFn)
}
}
// delete stale slots
for (const name of renderedKeys) {
if (
!(isArray(dynamicSlot)
Expand All @@ -95,14 +103,16 @@ export function initSlots(

function cleanupSlot(level: number) {
const restoreSlotNames: string[] = []
// remove slots from all queues
Object.keys(slotsQueue).forEach(slotName => {
const index = slotsQueue[slotName].findIndex(([l]) => l === level)
if (index > -1) {
slotsQueue[slotName].splice(index, 1)
slotsQueue[slotName] = slotsQueue[slotName].filter(([l]) => l !== level)
if (!slotsQueue[slotName].length) {
delete slotsQueue[slotName]
return
}
// restore next slot if the removed slots was the highest level slot
if (index === 0) {
renderedSlotKeys[level] && renderedSlotKeys[level].delete(slotName)
restoreSlotNames.push(slotName)
Expand All @@ -121,13 +131,15 @@ export function initSlots(
slotsQueue[name] ||= []
slotsQueue[name].push([level, slot])
slotsQueue[name].sort((a, b) => b[0] - a[0])
// hide old slot if the registered slot is the highest level
if (slotsQueue[name][1]) {
const hidenLevel = slotsQueue[name][1][0]
renderedSlotKeys[hidenLevel] && renderedSlotKeys[hidenLevel].delete(name)
}
if (slotsQueue[name][0][0] === level) {
renderedKeys && renderedKeys.add(name)
}
// render the highest level slot
addSlot(name, slotsQueue[name][0][1])
}

Expand Down

0 comments on commit ec13986

Please sign in to comment.