hydration.spec.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import Vue from 'vue'
  2. import { patch } from 'web/runtime/patch'
  3. import VNode from 'core/vdom/vnode'
  4. describe('hydration', () => {
  5. let vnode0
  6. beforeEach(() => {
  7. vnode0 = new VNode('p', { attrs: { id: '1' }}, [createTextVNode('hello world')])
  8. patch(null, vnode0)
  9. })
  10. it('should hydrate elements when server-rendered DOM tree is same as virtual DOM tree', () => {
  11. const result = []
  12. function init (vnode) { result.push(vnode) }
  13. function createServerRenderedDOM () {
  14. const root = document.createElement('div')
  15. root.setAttribute('server-rendered', 'true')
  16. const span = document.createElement('span')
  17. root.appendChild(span)
  18. const div = document.createElement('div')
  19. const child1 = document.createElement('span')
  20. const child2 = document.createElement('span')
  21. child1.textContent = 'hi'
  22. child2.textContent = 'ho'
  23. div.appendChild(child1)
  24. div.appendChild(child2)
  25. root.appendChild(div)
  26. return root
  27. }
  28. const node0 = createServerRenderedDOM()
  29. const vnode1 = new VNode('div', {}, [
  30. new VNode('span', {}),
  31. new VNode('div', { hook: { init }}, [
  32. new VNode('span', {}, [new VNode(undefined, undefined, undefined, 'hi')]),
  33. new VNode('span', {}, [new VNode(undefined, undefined, undefined, 'ho')])
  34. ])
  35. ])
  36. patch(node0, vnode1)
  37. expect(result.length).toBe(1)
  38. function traverseAndAssert (vnode, element) {
  39. expect(vnode.elm).toBe(element)
  40. if (vnode.children) {
  41. vnode.children.forEach((node, i) => {
  42. traverseAndAssert(node, element.childNodes[i])
  43. })
  44. }
  45. }
  46. // ensure vnodes are correctly associated with actual DOM
  47. traverseAndAssert(vnode1, node0)
  48. // check update
  49. const vnode2 = new VNode('div', { props: { id: 'foo' }}, [
  50. new VNode('span', { props: { id: 'bar' }}),
  51. new VNode('div', { hook: { init }}, [
  52. new VNode('span', {}),
  53. new VNode('span', {})
  54. ])
  55. ])
  56. patch(vnode1, vnode2)
  57. expect(node0.id).toBe('foo')
  58. expect(node0.children[0].id).toBe('bar')
  59. })
  60. it('should warn message that virtual DOM tree is not matching when hydrate element', () => {
  61. function createServerRenderedDOM () {
  62. const root = document.createElement('div')
  63. root.setAttribute('server-rendered', 'true')
  64. const span = document.createElement('span')
  65. root.appendChild(span)
  66. const div = document.createElement('div')
  67. const child1 = document.createElement('span')
  68. div.appendChild(child1)
  69. root.appendChild(div)
  70. return root
  71. }
  72. const node0 = createServerRenderedDOM()
  73. const vnode1 = new VNode('div', {}, [
  74. new VNode('span', {}),
  75. new VNode('div', {}, [
  76. new VNode('span', {}),
  77. new VNode('span', {})
  78. ])
  79. ])
  80. patch(node0, vnode1)
  81. expect('The client-side rendered virtual DOM tree is not matching').toHaveBeenWarned()
  82. })
  83. // component hydration is better off with a more e2e approach
  84. it('should hydrate components when server-rendered DOM tree is same as virtual DOM tree', done => {
  85. const dom = document.createElement('div')
  86. dom.setAttribute('server-rendered', 'true')
  87. dom.innerHTML = '<span>foo</span><div class="b a"><span>foo qux</span></div>'
  88. const originalNode1 = dom.children[0]
  89. const originalNode2 = dom.children[1]
  90. const vm = new Vue({
  91. template: '<div><span>{{msg}}</span><test class="a" :msg="msg"></test></div>',
  92. data: {
  93. msg: 'foo'
  94. },
  95. components: {
  96. test: {
  97. props: ['msg'],
  98. data () {
  99. return { a: 'qux' }
  100. },
  101. template: '<div class="b"><span>{{msg}} {{a}}</span></div>'
  102. }
  103. }
  104. })
  105. expect(() => { vm.$mount(dom) }).not.toThrow()
  106. expect('not matching server-rendered content').not.toHaveBeenWarned()
  107. expect(vm.$el).toBe(dom)
  108. expect(vm.$children[0].$el).toBe(originalNode2)
  109. expect(vm.$el.children[0]).toBe(originalNode1)
  110. expect(vm.$el.children[1]).toBe(originalNode2)
  111. vm.msg = 'bar'
  112. waitForUpdate(() => {
  113. expect(vm.$el.innerHTML).toBe('<span>bar</span><div class="b a"><span>bar qux</span></div>')
  114. vm.$children[0].a = 'ququx'
  115. }).then(() => {
  116. expect(vm.$el.innerHTML).toBe('<span>bar</span><div class="b a"><span>bar ququx</span></div>')
  117. }).then(done)
  118. })
  119. it('should warn failed hydration for non-matching DOM in child component', () => {
  120. const dom = document.createElement('div')
  121. dom.setAttribute('server-rendered', 'true')
  122. dom.innerHTML = '<div><span>foo</span></div>'
  123. const vm = new Vue({
  124. template: '<div><test :msg="msg"></test></div>',
  125. components: {
  126. test: {
  127. data () {
  128. return { a: 'qux' }
  129. },
  130. template: '<div><span>{{a}}</span></div>'
  131. }
  132. }
  133. })
  134. vm.$mount(dom)
  135. expect('not matching server-rendered content').toHaveBeenWarned()
  136. })
  137. })