componentSlots.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import {
  2. getCurrentInstance,
  3. h,
  4. nextTick,
  5. nodeOps,
  6. ref,
  7. render,
  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 updated 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 updated 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. footer: undefined,
  117. }
  118. const newSlots = {
  119. header: undefined,
  120. footer: 'footer',
  121. }
  122. const Comp = {
  123. setup() {
  124. return () => [
  125. h(Child, { n: flag1.value }, flag1.value ? oldSlots : newSlots),
  126. ]
  127. },
  128. }
  129. render(h(Comp), nodeOps.createElement('div'))
  130. expect(instance.slots).toHaveProperty('header')
  131. expect(instance.slots).not.toHaveProperty('footer')
  132. flag1.value = false
  133. await nextTick()
  134. expect(
  135. '[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
  136. ).toHaveBeenWarned()
  137. expect(
  138. '[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
  139. ).toHaveBeenWarned()
  140. expect(instance.slots).not.toHaveProperty('header')
  141. expect(instance.slots.footer()).toMatchObject([normalizeVNode('footer')])
  142. })
  143. test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
  144. const flag1 = ref(true)
  145. let instance: any
  146. const Child = () => {
  147. instance = getCurrentInstance()
  148. return 'child'
  149. }
  150. const Comp = {
  151. setup() {
  152. return () => [
  153. h(Child, { n: flag1.value }, flag1.value ? ['header'] : ['footer']),
  154. ]
  155. },
  156. }
  157. render(h(Comp), nodeOps.createElement('div'))
  158. expect(instance.slots.default()).toMatchObject([normalizeVNode('header')])
  159. flag1.value = false
  160. await nextTick()
  161. expect(
  162. '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
  163. ).toHaveBeenWarned()
  164. expect(instance.slots.default()).toMatchObject([normalizeVNode('footer')])
  165. })
  166. test('should respect $stable flag', async () => {
  167. const flag1 = ref(1)
  168. const flag2 = ref(2)
  169. const spy = vi.fn()
  170. const Child = () => {
  171. spy()
  172. return 'child'
  173. }
  174. const App = {
  175. setup() {
  176. return () => [
  177. flag1.value,
  178. h(
  179. Child,
  180. { n: flag2.value },
  181. {
  182. foo: () => 'foo',
  183. $stable: true,
  184. },
  185. ),
  186. ]
  187. },
  188. }
  189. render(h(App), nodeOps.createElement('div'))
  190. expect(spy).toHaveBeenCalledTimes(1)
  191. // parent re-render, props didn't change, slots are stable
  192. // -> child should not update
  193. flag1.value++
  194. await nextTick()
  195. expect(spy).toHaveBeenCalledTimes(1)
  196. // parent re-render, props changed
  197. // -> child should update
  198. flag2.value++
  199. await nextTick()
  200. expect(spy).toHaveBeenCalledTimes(2)
  201. })
  202. })