componentSlots.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { vi } from 'vitest'
  2. import {
  3. ref,
  4. render,
  5. h,
  6. nodeOps,
  7. nextTick,
  8. getCurrentInstance
  9. } from '@vue/runtime-test'
  10. import { normalizeVNode } from '../src/vnode'
  11. import { createSlots } from '../src/helpers/createSlots'
  12. describe('component: slots', () => {
  13. function renderWithSlots(slots: any): any {
  14. let instance: any
  15. const Comp = {
  16. render() {
  17. instance = getCurrentInstance()
  18. return h('div')
  19. }
  20. }
  21. render(h(Comp, null, slots), nodeOps.createElement('div'))
  22. return instance
  23. }
  24. test('initSlots: instance.slots should be set correctly', () => {
  25. const { slots } = renderWithSlots({ _: 1 })
  26. expect(slots).toMatchObject({ _: 1 })
  27. })
  28. test('initSlots: should normalize object slots (when value is null, string, array)', () => {
  29. const { slots } = renderWithSlots({
  30. _inner: '_inner',
  31. foo: null,
  32. header: 'header',
  33. footer: ['f1', 'f2']
  34. })
  35. expect(
  36. '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
  37. ).toHaveBeenWarned()
  38. expect(
  39. '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
  40. ).toHaveBeenWarned()
  41. expect(slots).not.toHaveProperty('_inner')
  42. expect(slots).not.toHaveProperty('foo')
  43. expect(slots.header()).toMatchObject([normalizeVNode('header')])
  44. expect(slots.footer()).toMatchObject([
  45. normalizeVNode('f1'),
  46. normalizeVNode('f2')
  47. ])
  48. })
  49. test('initSlots: should normalize object slots (when value is function)', () => {
  50. let proxy: any
  51. const Comp = {
  52. render() {
  53. proxy = getCurrentInstance()
  54. return h('div')
  55. }
  56. }
  57. render(
  58. h(Comp, null, {
  59. header: () => 'header'
  60. }),
  61. nodeOps.createElement('div')
  62. )
  63. expect(proxy.slots.header()).toMatchObject([normalizeVNode('header')])
  64. })
  65. test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
  66. const { slots } = renderWithSlots([h('span')])
  67. expect(
  68. '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
  69. ).toHaveBeenWarned()
  70. expect(slots.default()).toMatchObject([normalizeVNode(h('span'))])
  71. })
  72. test('updateSlots: instance.slots should be updated correctly (when slotType is number)', async () => {
  73. const flag1 = ref(true)
  74. let instance: any
  75. const Child = () => {
  76. instance = getCurrentInstance()
  77. return 'child'
  78. }
  79. const Comp = {
  80. setup() {
  81. return () => [
  82. h(
  83. Child,
  84. null,
  85. createSlots({ _: 2 as any }, [
  86. flag1.value
  87. ? {
  88. name: 'one',
  89. fn: () => [h('span')]
  90. }
  91. : {
  92. name: 'two',
  93. fn: () => [h('div')]
  94. }
  95. ])
  96. )
  97. ]
  98. }
  99. }
  100. render(h(Comp), nodeOps.createElement('div'))
  101. expect(instance.slots).toHaveProperty('one')
  102. expect(instance.slots).not.toHaveProperty('two')
  103. flag1.value = false
  104. await nextTick()
  105. expect(instance.slots).not.toHaveProperty('one')
  106. expect(instance.slots).toHaveProperty('two')
  107. })
  108. test('updateSlots: instance.slots should be updated correctly (when slotType is null)', async () => {
  109. const flag1 = ref(true)
  110. let instance: any
  111. const Child = () => {
  112. instance = getCurrentInstance()
  113. return 'child'
  114. }
  115. const oldSlots = {
  116. header: 'header'
  117. }
  118. const newSlots = {
  119. footer: 'footer'
  120. }
  121. const Comp = {
  122. setup() {
  123. return () => [
  124. h(Child, { n: flag1.value }, flag1.value ? oldSlots : newSlots)
  125. ]
  126. }
  127. }
  128. render(h(Comp), nodeOps.createElement('div'))
  129. expect(instance.slots).toHaveProperty('header')
  130. expect(instance.slots).not.toHaveProperty('footer')
  131. flag1.value = false
  132. await nextTick()
  133. expect(
  134. '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.'
  135. ).toHaveBeenWarned()
  136. expect(
  137. '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.'
  138. ).toHaveBeenWarned()
  139. expect(instance.slots).not.toHaveProperty('header')
  140. expect(instance.slots.footer()).toMatchObject([normalizeVNode('footer')])
  141. })
  142. test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
  143. const flag1 = ref(true)
  144. let instance: any
  145. const Child = () => {
  146. instance = getCurrentInstance()
  147. return 'child'
  148. }
  149. const Comp = {
  150. setup() {
  151. return () => [
  152. h(Child, { n: flag1.value }, flag1.value ? ['header'] : ['footer'])
  153. ]
  154. }
  155. }
  156. render(h(Comp), nodeOps.createElement('div'))
  157. expect(instance.slots.default()).toMatchObject([normalizeVNode('header')])
  158. flag1.value = false
  159. await nextTick()
  160. expect(
  161. '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.'
  162. ).toHaveBeenWarned()
  163. expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')])
  164. })
  165. test('should respect $stable flag', async () => {
  166. const flag1 = ref(1)
  167. const flag2 = ref(2)
  168. const spy = vi.fn()
  169. const Child = () => {
  170. spy()
  171. return 'child'
  172. }
  173. const App = {
  174. setup() {
  175. return () => [
  176. flag1.value,
  177. h(
  178. Child,
  179. { n: flag2.value },
  180. {
  181. foo: () => 'foo',
  182. $stable: true
  183. }
  184. )
  185. ]
  186. }
  187. }
  188. render(h(App), nodeOps.createElement('div'))
  189. expect(spy).toHaveBeenCalledTimes(1)
  190. // parent re-render, props didn't change, slots are stable
  191. // -> child should not update
  192. flag1.value++
  193. await nextTick()
  194. expect(spy).toHaveBeenCalledTimes(1)
  195. // parent re-render, props changed
  196. // -> child should update
  197. flag2.value++
  198. await nextTick()
  199. expect(spy).toHaveBeenCalledTimes(2)
  200. })
  201. })