2020-08-27 00:17:21 +08:00
|
|
|
import {
|
|
|
|
ref,
|
|
|
|
render,
|
|
|
|
h,
|
|
|
|
nodeOps,
|
|
|
|
nextTick,
|
|
|
|
getCurrentInstance
|
|
|
|
} from '@vue/runtime-test'
|
|
|
|
import { normalizeVNode } from '../src/vnode'
|
|
|
|
import { createSlots } from '../src/helpers/createSlots'
|
2020-04-10 22:59:46 +08:00
|
|
|
|
|
|
|
describe('component: slots', () => {
|
2020-08-27 00:17:21 +08:00
|
|
|
function renderWithSlots(slots: any): any {
|
|
|
|
let instance: any
|
|
|
|
const Comp = {
|
|
|
|
render() {
|
|
|
|
instance = getCurrentInstance()
|
|
|
|
return h('div')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(h(Comp, null, slots), nodeOps.createElement('div'))
|
|
|
|
return instance
|
|
|
|
}
|
|
|
|
|
|
|
|
test('initSlots: instance.slots should be set correctly', () => {
|
|
|
|
const { slots } = renderWithSlots({ _: 1 })
|
|
|
|
expect(slots).toMatchObject({ _: 1 })
|
|
|
|
})
|
|
|
|
|
|
|
|
test('initSlots: should normalize object slots (when value is null, string, array)', () => {
|
|
|
|
const { slots } = renderWithSlots({
|
|
|
|
_inner: '_inner',
|
|
|
|
foo: null,
|
|
|
|
header: 'header',
|
|
|
|
footer: ['f1', 'f2']
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(slots).not.toHaveProperty('_inner')
|
|
|
|
expect(slots).not.toHaveProperty('foo')
|
|
|
|
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
|
|
|
expect(slots.footer()).toMatchObject([
|
|
|
|
normalizeVNode('f1'),
|
|
|
|
normalizeVNode('f2')
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
test('initSlots: should normalize object slots (when value is function)', () => {
|
|
|
|
let proxy: any
|
|
|
|
const Comp = {
|
|
|
|
render() {
|
|
|
|
proxy = getCurrentInstance()
|
|
|
|
return h('div')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render(
|
|
|
|
h(Comp, null, {
|
|
|
|
header: () => 'header'
|
|
|
|
}),
|
|
|
|
nodeOps.createElement('div')
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(proxy.slots.header()).toMatchObject([normalizeVNode('header')])
|
|
|
|
})
|
|
|
|
|
|
|
|
test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
|
|
|
|
const { slots } = renderWithSlots([h('span')])
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
|
|
|
|
})
|
|
|
|
|
2021-04-22 03:09:18 +08:00
|
|
|
test('updateSlots: instance.slots should be updated correctly (when slotType is number)', async () => {
|
2020-08-27 00:17:21 +08:00
|
|
|
const flag1 = ref(true)
|
|
|
|
|
|
|
|
let instance: any
|
|
|
|
const Child = () => {
|
|
|
|
instance = getCurrentInstance()
|
|
|
|
return 'child'
|
|
|
|
}
|
|
|
|
|
|
|
|
const Comp = {
|
|
|
|
setup() {
|
|
|
|
return () => [
|
|
|
|
h(
|
|
|
|
Child,
|
|
|
|
null,
|
|
|
|
createSlots({ _: 2 as any }, [
|
|
|
|
flag1.value
|
|
|
|
? {
|
|
|
|
name: 'one',
|
|
|
|
fn: () => [h('span')]
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
name: 'two',
|
|
|
|
fn: () => [h('div')]
|
|
|
|
}
|
|
|
|
])
|
|
|
|
)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
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')
|
|
|
|
})
|
|
|
|
|
2021-04-22 03:09:18 +08:00
|
|
|
test('updateSlots: instance.slots should be updated correctly (when slotType is null)', async () => {
|
2020-08-27 00:17:21 +08:00
|
|
|
const flag1 = ref(true)
|
|
|
|
|
|
|
|
let instance: any
|
|
|
|
const Child = () => {
|
|
|
|
instance = getCurrentInstance()
|
|
|
|
return 'child'
|
|
|
|
}
|
|
|
|
|
|
|
|
const oldSlots = {
|
|
|
|
header: 'header'
|
|
|
|
}
|
|
|
|
const newSlots = {
|
|
|
|
footer: 'footer'
|
|
|
|
}
|
|
|
|
|
|
|
|
const Comp = {
|
|
|
|
setup() {
|
|
|
|
return () => [
|
|
|
|
h(Child, { n: flag1.value }, flag1.value ? oldSlots : newSlots)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(instance.slots).toHaveProperty('header')
|
|
|
|
expect(instance.slots).not.toHaveProperty('footer')
|
|
|
|
|
|
|
|
flag1.value = false
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(instance.slots).not.toHaveProperty('header')
|
|
|
|
expect(instance.slots.footer()).toMatchObject([normalizeVNode('footer')])
|
|
|
|
})
|
|
|
|
|
|
|
|
test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
|
|
|
|
const flag1 = ref(true)
|
|
|
|
|
|
|
|
let instance: any
|
|
|
|
const Child = () => {
|
|
|
|
instance = getCurrentInstance()
|
|
|
|
return 'child'
|
|
|
|
}
|
|
|
|
|
|
|
|
const Comp = {
|
|
|
|
setup() {
|
|
|
|
return () => [
|
|
|
|
h(Child, { n: flag1.value }, flag1.value ? ['header'] : ['footer'])
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
render(h(Comp), nodeOps.createElement('div'))
|
|
|
|
|
|
|
|
expect(instance.slots.default()).toMatchObject([normalizeVNode('header')])
|
|
|
|
|
|
|
|
flag1.value = false
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
expect(
|
|
|
|
'[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
|
|
|
|
).toHaveBeenWarned()
|
|
|
|
|
|
|
|
expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')])
|
|
|
|
})
|
2020-04-10 22:59:46 +08:00
|
|
|
|
|
|
|
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 stable
|
|
|
|
// -> 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)
|
|
|
|
})
|
|
|
|
})
|