componentSlots.spec.ts 6.1 KB

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