rendererComponent.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import {
  2. ref,
  3. h,
  4. render,
  5. nodeOps,
  6. serializeInner,
  7. nextTick,
  8. VNode,
  9. provide,
  10. inject,
  11. Ref,
  12. watch,
  13. SetupContext
  14. } from '@vue/runtime-test'
  15. describe('renderer: component', () => {
  16. test('should update parent(hoc) component host el when child component self update', async () => {
  17. const value = ref(true)
  18. let parentVnode: VNode
  19. let childVnode1: VNode
  20. let childVnode2: VNode
  21. const Parent = {
  22. render: () => {
  23. // let Parent first rerender
  24. return (parentVnode = h(Child))
  25. }
  26. }
  27. const Child = {
  28. render: () => {
  29. return value.value
  30. ? (childVnode1 = h('div'))
  31. : (childVnode2 = h('span'))
  32. }
  33. }
  34. const root = nodeOps.createElement('div')
  35. render(h(Parent), root)
  36. expect(serializeInner(root)).toBe(`<div></div>`)
  37. expect(parentVnode!.el).toBe(childVnode1!.el)
  38. value.value = false
  39. await nextTick()
  40. expect(serializeInner(root)).toBe(`<span></span>`)
  41. expect(parentVnode!.el).toBe(childVnode2!.el)
  42. })
  43. it('should create an Component with props', () => {
  44. const Comp = {
  45. render: () => {
  46. return h('div')
  47. }
  48. }
  49. const root = nodeOps.createElement('div')
  50. render(h(Comp, { id: 'foo', class: 'bar' }), root)
  51. expect(serializeInner(root)).toBe(`<div id="foo" class="bar"></div>`)
  52. })
  53. it('should create an Component with direct text children', () => {
  54. const Comp = {
  55. render: () => {
  56. return h('div', 'test')
  57. }
  58. }
  59. const root = nodeOps.createElement('div')
  60. render(h(Comp, { id: 'foo', class: 'bar' }), root)
  61. expect(serializeInner(root)).toBe(`<div id="foo" class="bar">test</div>`)
  62. })
  63. it('should update an Component tag which is already mounted', () => {
  64. const Comp1 = {
  65. render: () => {
  66. return h('div', 'foo')
  67. }
  68. }
  69. const root = nodeOps.createElement('div')
  70. render(h(Comp1), root)
  71. expect(serializeInner(root)).toBe('<div>foo</div>')
  72. const Comp2 = {
  73. render: () => {
  74. return h('span', 'foo')
  75. }
  76. }
  77. render(h(Comp2), root)
  78. expect(serializeInner(root)).toBe('<span>foo</span>')
  79. })
  80. // #2072
  81. it('should not update Component if only changed props are declared emit listeners', () => {
  82. const Comp1 = {
  83. emits: ['foo'],
  84. updated: jest.fn(),
  85. render: () => null
  86. }
  87. const root = nodeOps.createElement('div')
  88. render(
  89. h(Comp1, {
  90. onFoo: () => {}
  91. }),
  92. root
  93. )
  94. render(
  95. h(Comp1, {
  96. onFoo: () => {}
  97. }),
  98. root
  99. )
  100. expect(Comp1.updated).not.toHaveBeenCalled()
  101. })
  102. // #2043
  103. test('component child synchronously updating parent state should trigger parent re-render', async () => {
  104. const App = {
  105. setup() {
  106. const n = ref(0)
  107. provide('foo', n)
  108. return () => {
  109. return [h('div', n.value), h(Child)]
  110. }
  111. }
  112. }
  113. const Child = {
  114. setup() {
  115. const n = inject<Ref<number>>('foo')!
  116. n.value++
  117. return () => {
  118. return h('div', n.value)
  119. }
  120. }
  121. }
  122. const root = nodeOps.createElement('div')
  123. render(h(App), root)
  124. expect(serializeInner(root)).toBe(`<div>0</div><div>1</div>`)
  125. await nextTick()
  126. expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
  127. })
  128. // #2170
  129. test('should have access to instance’s “$el” property in watcher when rendereing with watched prop', async () => {
  130. function returnThis(this: any) {
  131. return this
  132. }
  133. const propWatchSpy = jest.fn(returnThis)
  134. let instance: any
  135. const Comp = {
  136. props: {
  137. testProp: String
  138. },
  139. watch: {
  140. testProp() {
  141. // @ts-ignore
  142. propWatchSpy(this.$el)
  143. }
  144. },
  145. created() {
  146. instance = this
  147. },
  148. render() {
  149. return h('div')
  150. }
  151. }
  152. const root = nodeOps.createElement('div')
  153. render(h(Comp), root)
  154. await nextTick()
  155. expect(propWatchSpy).not.toHaveBeenCalled()
  156. render(h(Comp, { testProp: 'prop ' }), root)
  157. await nextTick()
  158. expect(propWatchSpy).toHaveBeenCalledWith(instance.$el)
  159. })
  160. // #2200
  161. test('component child updating parent state in pre-flush should trigger parent re-render', async () => {
  162. const outer = ref(0)
  163. const App = {
  164. setup() {
  165. const inner = ref(0)
  166. return () => {
  167. return [
  168. h('div', inner.value),
  169. h(Child, {
  170. value: outer.value,
  171. onUpdate: (val: number) => (inner.value = val)
  172. })
  173. ]
  174. }
  175. }
  176. }
  177. const Child = {
  178. props: ['value'],
  179. setup(props: any, { emit }: SetupContext) {
  180. watch(() => props.value, (val: number) => emit('update', val))
  181. return () => {
  182. return h('div', props.value)
  183. }
  184. }
  185. }
  186. const root = nodeOps.createElement('div')
  187. render(h(App), root)
  188. expect(serializeInner(root)).toBe(`<div>0</div><div>0</div>`)
  189. outer.value++
  190. await nextTick()
  191. expect(serializeInner(root)).toBe(`<div>1</div><div>1</div>`)
  192. })
  193. })