+import { h, ref, render, nodeOps, nextTick } from '@vue/runtime-test'
+
describe('renderer: component', () => {
test.todo('should work')
test.todo('componentProps')
- test.todo('componentSlots')
+ describe('slots', () => {
+ test('should respect $stable flag', async () => {
+ const flag1 = ref(1)
+ const flag2 = ref(2)
+ const spy = jest.fn()
+
+ const Child = () => {
+ spy()
+ return 'child'
+ }
+
+ const App = {
+ setup() {
+ return () => [
+ flag1.value,
+ h(
+ Child,
+ { n: flag2.value },
+ {
+ foo: () => 'foo',
+ $stable: true
+ }
+ )
+ ]
+ }
+ }
+
+ render(h(App), nodeOps.createElement('div'))
+ expect(spy).toHaveBeenCalledTimes(1)
+
+ // parent re-render, props didn't change, slots are stasble
+ // -> child should not update
+ flag1.value++
+ await nextTick()
+ expect(spy).toHaveBeenCalledTimes(1)
+
+ // parent re-render, props changed
+ // -> child should update
+ flag2.value++
+ await nextTick()
+ expect(spy).toHaveBeenCalledTimes(2)
+ })
+ })
})
// this path is only taken by manually written render functions
// so presence of any children leads to a forced update
if (prevChildren != null || nextChildren != null) {
- return true
+ if (nextChildren == null || !(nextChildren as any).$stable) {
+ return true
+ }
}
if (prevProps === nextProps) {
return false
export type RawSlots = {
[name: string]: unknown
+ // manual render fn hint to skip forced children updates
+ $stable?: boolean
+ // internal, indicates compiler generated slots = can skip normalization
_compiled?: boolean
}
} else {
slots = {}
for (const key in rawSlots) {
+ if (key === '$stable') continue
const value = rawSlots[key]
if (isFunction(value)) {
slots[key] = normalizeSlot(key, value)