vnode.spec.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import { createBlock, createVNode, openBlock } from '@vue/runtime-test'
  2. import {
  3. ShapeFlags,
  4. Comment,
  5. Fragment,
  6. Text,
  7. cloneVNode
  8. } from '@vue/runtime-core'
  9. import { mergeProps, normalizeVNode } from '../src/vnode'
  10. import { Data } from '../src/component'
  11. describe('vnode', () => {
  12. test('create with just tag', () => {
  13. const vnode = createVNode('p')
  14. expect(vnode.type).toBe('p')
  15. expect(vnode.props).toBe(null)
  16. })
  17. test('create with tag and props', () => {
  18. const vnode = createVNode('p', {})
  19. expect(vnode.type).toBe('p')
  20. expect(vnode.props).toMatchObject({})
  21. })
  22. test('create with tag, props and children', () => {
  23. const vnode = createVNode('p', {}, ['foo'])
  24. expect(vnode.type).toBe('p')
  25. expect(vnode.props).toMatchObject({})
  26. expect(vnode.children).toMatchObject(['foo'])
  27. })
  28. test('create with 0 as props', () => {
  29. const vnode = createVNode('p', null)
  30. expect(vnode.type).toBe('p')
  31. expect(vnode.props).toBe(null)
  32. })
  33. describe('class normalization', () => {
  34. test('string', () => {
  35. const vnode = createVNode('p', { class: 'foo baz' })
  36. expect(vnode.props).toMatchObject({ class: 'foo baz' })
  37. })
  38. test('array<string>', () => {
  39. const vnode = createVNode('p', { class: ['foo', 'baz'] })
  40. expect(vnode.props).toMatchObject({ class: 'foo baz' })
  41. })
  42. test('array<object>', () => {
  43. const vnode = createVNode('p', {
  44. class: [{ foo: 'foo' }, { baz: 'baz' }]
  45. })
  46. expect(vnode.props).toMatchObject({ class: 'foo baz' })
  47. })
  48. test('object', () => {
  49. const vnode = createVNode('p', { class: { foo: 'foo', baz: 'baz' } })
  50. expect(vnode.props).toMatchObject({ class: 'foo baz' })
  51. })
  52. })
  53. describe('style normalization', () => {
  54. test('array', () => {
  55. const vnode = createVNode('p', {
  56. style: [{ foo: 'foo' }, { baz: 'baz' }]
  57. })
  58. expect(vnode.props).toMatchObject({ style: { foo: 'foo', baz: 'baz' } })
  59. })
  60. test('object', () => {
  61. const vnode = createVNode('p', { style: { foo: 'foo', baz: 'baz' } })
  62. expect(vnode.props).toMatchObject({ style: { foo: 'foo', baz: 'baz' } })
  63. })
  64. })
  65. describe('children normalization', () => {
  66. const nop = jest.fn
  67. test('null', () => {
  68. const vnode = createVNode('p', null, null)
  69. expect(vnode.children).toBe(null)
  70. expect(vnode.shapeFlag).toBe(ShapeFlags.ELEMENT)
  71. })
  72. test('array', () => {
  73. const vnode = createVNode('p', null, ['foo'])
  74. expect(vnode.children).toMatchObject(['foo'])
  75. expect(vnode.shapeFlag).toBe(
  76. ShapeFlags.ELEMENT + ShapeFlags.ARRAY_CHILDREN
  77. )
  78. })
  79. test('object', () => {
  80. const vnode = createVNode('p', null, { foo: 'foo' })
  81. expect(vnode.children).toMatchObject({ foo: 'foo' })
  82. expect(vnode.shapeFlag).toBe(
  83. ShapeFlags.ELEMENT + ShapeFlags.SLOTS_CHILDREN
  84. )
  85. })
  86. test('function', () => {
  87. const vnode = createVNode('p', null, nop)
  88. expect(vnode.children).toMatchObject({ default: nop })
  89. expect(vnode.shapeFlag).toBe(
  90. ShapeFlags.ELEMENT + ShapeFlags.SLOTS_CHILDREN
  91. )
  92. })
  93. test('string', () => {
  94. const vnode = createVNode('p', null, 'foo')
  95. expect(vnode.children).toBe('foo')
  96. expect(vnode.shapeFlag).toBe(
  97. ShapeFlags.ELEMENT + ShapeFlags.TEXT_CHILDREN
  98. )
  99. })
  100. })
  101. test('normalizeVNode', () => {
  102. // null / undefined -> Comment
  103. expect(normalizeVNode(null)).toMatchObject({ type: Comment })
  104. expect(normalizeVNode(undefined)).toMatchObject({ type: Comment })
  105. // boolean -> Comment
  106. // this is for usage like `someBoolean && h('div')` and behavior consistency
  107. // with 2.x (#574)
  108. expect(normalizeVNode(true)).toMatchObject({ type: Comment })
  109. expect(normalizeVNode(false)).toMatchObject({ type: Comment })
  110. // array -> Fragment
  111. expect(normalizeVNode(['foo'])).toMatchObject({ type: Fragment })
  112. // VNode -> VNode
  113. const vnode = createVNode('div')
  114. expect(normalizeVNode(vnode)).toBe(vnode)
  115. // mounted VNode -> cloned VNode
  116. const mounted = createVNode('div')
  117. mounted.el = {}
  118. const normalized = normalizeVNode(mounted)
  119. expect(normalized).not.toBe(mounted)
  120. expect(normalized).toEqual(mounted)
  121. // primitive types
  122. expect(normalizeVNode('foo')).toMatchObject({ type: Text, children: `foo` })
  123. expect(normalizeVNode(1)).toMatchObject({ type: Text, children: `1` })
  124. })
  125. test('type shapeFlag inference', () => {
  126. expect(createVNode('div').shapeFlag).toBe(ShapeFlags.ELEMENT)
  127. expect(createVNode({}).shapeFlag).toBe(ShapeFlags.STATEFUL_COMPONENT)
  128. expect(createVNode(() => {}).shapeFlag).toBe(
  129. ShapeFlags.FUNCTIONAL_COMPONENT
  130. )
  131. expect(createVNode(Text).shapeFlag).toBe(0)
  132. })
  133. test('cloneVNode', () => {
  134. const node1 = createVNode('div', { foo: 1 }, null)
  135. expect(cloneVNode(node1)).toEqual(node1)
  136. const node2 = createVNode({}, null, [node1])
  137. const cloned2 = cloneVNode(node2)
  138. expect(cloned2).toEqual(node2)
  139. expect(cloneVNode(node2)).toEqual(node2)
  140. expect(cloneVNode(node2)).toEqual(cloned2)
  141. })
  142. describe('mergeProps', () => {
  143. test('class', () => {
  144. let props1: Data = { class: 'c' }
  145. let props2: Data = { class: ['cc'] }
  146. let props3: Data = { class: [{ ccc: true }] }
  147. let props4: Data = { class: { cccc: true } }
  148. expect(mergeProps(props1, props2, props3, props4)).toMatchObject({
  149. class: 'c cc ccc cccc'
  150. })
  151. })
  152. test('style', () => {
  153. let props1: Data = {
  154. style: {
  155. color: 'red',
  156. fontSize: 10
  157. }
  158. }
  159. let props2: Data = {
  160. style: [
  161. {
  162. color: 'blue',
  163. width: '200px'
  164. },
  165. {
  166. width: '300px',
  167. height: '300px',
  168. fontSize: 30
  169. }
  170. ]
  171. }
  172. expect(mergeProps(props1, props2)).toMatchObject({
  173. style: {
  174. color: 'blue',
  175. width: '300px',
  176. height: '300px',
  177. fontSize: 30
  178. }
  179. })
  180. })
  181. test('handlers', () => {
  182. let clickHander1 = function() {}
  183. let clickHander2 = function() {}
  184. let focusHander2 = function() {}
  185. let props1: Data = { onClick: clickHander1 }
  186. let props2: Data = { onClick: clickHander2, onFocus: focusHander2 }
  187. expect(mergeProps(props1, props2)).toMatchObject({
  188. onClick: [clickHander1, clickHander2],
  189. onFocus: focusHander2
  190. })
  191. })
  192. test('default', () => {
  193. let props1: Data = { foo: 'c' }
  194. let props2: Data = { foo: {}, bar: ['cc'] }
  195. let props3: Data = { baz: { ccc: true } }
  196. expect(mergeProps(props1, props2, props3)).toMatchObject({
  197. foo: {},
  198. bar: ['cc'],
  199. baz: { ccc: true }
  200. })
  201. })
  202. })
  203. describe('dynamic children', () => {
  204. test('with patchFlags', () => {
  205. const hoist = createVNode('div')
  206. let vnode1
  207. const vnode = (openBlock(),
  208. createBlock('div', null, [
  209. hoist,
  210. (vnode1 = createVNode('div', null, 'text', 1 /* TEXT */))
  211. ]))
  212. expect(vnode.dynamicChildren).toStrictEqual([vnode1])
  213. })
  214. test('many times call openBlock', () => {
  215. const hoist = createVNode('div')
  216. let vnode1, vnode2, vnode3
  217. const vnode = (openBlock(),
  218. createBlock('div', null, [
  219. hoist,
  220. (vnode1 = createVNode('div', null, 'text', 1 /* TEXT */)),
  221. (vnode2 = (openBlock(),
  222. createBlock('div', null, [
  223. hoist,
  224. (vnode3 = createVNode('div', null, 'text', 1 /* TEXT */))
  225. ])))
  226. ]))
  227. expect(vnode.dynamicChildren).toStrictEqual([vnode1, vnode2])
  228. expect(vnode2.dynamicChildren).toStrictEqual([vnode3])
  229. })
  230. test('with stateful component', () => {
  231. const hoist = createVNode('div')
  232. let vnode1
  233. const vnode = (openBlock(),
  234. createBlock('div', null, [
  235. hoist,
  236. (vnode1 = createVNode({}, null, 'text'))
  237. ]))
  238. expect(vnode.dynamicChildren).toStrictEqual([vnode1])
  239. })
  240. test('with functional component', () => {
  241. const hoist = createVNode('div')
  242. let vnode1
  243. const vnode = (openBlock(),
  244. createBlock('div', null, [
  245. hoist,
  246. (vnode1 = createVNode(() => {}, null, 'text'))
  247. ]))
  248. expect(vnode.dynamicChildren).toStrictEqual([vnode1])
  249. })
  250. test('with suspense', () => {
  251. const hoist = createVNode('div')
  252. let vnode1
  253. const vnode = (openBlock(),
  254. createBlock('div', null, [
  255. hoist,
  256. (vnode1 = createVNode(() => {}, null, 'text'))
  257. ]))
  258. expect(vnode.dynamicChildren).toStrictEqual([vnode1])
  259. })
  260. })
  261. })