edge-cases.spec.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import Vue from 'vue'
  2. describe('vdom patch: edge cases', () => {
  3. // exposed by #3406
  4. // When a static vnode is inside v-for, it's possible for the same vnode
  5. // to be used in multiple places, and its element will be replaced. This
  6. // causes patch errors when node ops depend on the vnode's element position.
  7. it('should handle static vnodes by key', done => {
  8. const vm = new Vue({
  9. data: {
  10. ok: true
  11. },
  12. template: `
  13. <div>
  14. <div v-for="i in 2">
  15. <div v-if="ok">a</div><div>b</div><div v-if="!ok">c</div><div>d</div>
  16. </div>
  17. </div>
  18. `
  19. }).$mount()
  20. expect(vm.$el.textContent).toBe('abdabd')
  21. vm.ok = false
  22. waitForUpdate(() => {
  23. expect(vm.$el.textContent).toBe('bcdbcd')
  24. }).then(done)
  25. })
  26. // #3533
  27. // a static node (<br>) is reused in createElm, which changes its elm reference
  28. // and is inserted into a different parent.
  29. // later when patching the next element a DOM insertion uses it as the
  30. // reference node, causing a parent mismatch.
  31. it('should handle static node edge case when it\'s reused AND used as a reference node for insertion', done => {
  32. const vm = new Vue({
  33. data: {
  34. ok: true
  35. },
  36. template: `
  37. <div>
  38. <button @click="ok = !ok">toggle</button>
  39. <div class="b" v-if="ok">123</div>
  40. <div class="c">
  41. <br><p>{{ 1 }}</p>
  42. </div>
  43. <div class="d">
  44. <label>{{ 2 }}</label>
  45. </div>
  46. </div>
  47. `
  48. }).$mount()
  49. expect(vm.$el.querySelector('.c').textContent).toBe('1')
  50. expect(vm.$el.querySelector('.d').textContent).toBe('2')
  51. vm.ok = false
  52. waitForUpdate(() => {
  53. expect(vm.$el.querySelector('.c').textContent).toBe('1')
  54. expect(vm.$el.querySelector('.d').textContent).toBe('2')
  55. }).then(done)
  56. })
  57. it('should synchronize vm\' vnode', done => {
  58. const comp = {
  59. data: () => ({ swap: true }),
  60. render (h) {
  61. return this.swap
  62. ? h('a', 'atag')
  63. : h('span', 'span')
  64. }
  65. }
  66. const wrapper = {
  67. render: h => h('comp'),
  68. components: { comp }
  69. }
  70. const vm = new Vue({
  71. render (h) {
  72. const children = [
  73. h('wrapper'),
  74. h('div', 'row')
  75. ]
  76. if (this.swap) {
  77. children.reverse()
  78. }
  79. return h('div', children)
  80. },
  81. data: () => ({ swap: false }),
  82. components: { wrapper }
  83. }).$mount()
  84. expect(vm.$el.innerHTML).toBe('<a>atag</a><div>row</div>')
  85. const wrapperVm = vm.$children[0]
  86. const compVm = wrapperVm.$children[0]
  87. vm.swap = true
  88. waitForUpdate(() => {
  89. expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
  90. expect(vm.$el.innerHTML).toBe('<div>row</div><a>atag</a>')
  91. vm.swap = false
  92. }).then(() => {
  93. expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
  94. expect(vm.$el.innerHTML).toBe('<a>atag</a><div>row</div>')
  95. compVm.swap = false
  96. }).then(() => {
  97. expect(vm.$el.innerHTML).toBe('<span>span</span><div>row</div>')
  98. expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
  99. vm.swap = true
  100. }).then(() => {
  101. expect(vm.$el.innerHTML).toBe('<div>row</div><span>span</span>')
  102. expect(compVm.$vnode.parent).toBe(wrapperVm.$vnode)
  103. vm.swap = true
  104. }).then(done)
  105. })
  106. // #4530
  107. it('should not reset value when patching between dynamic/static bindings', done => {
  108. const vm = new Vue({
  109. data: { ok: true },
  110. template: `
  111. <div>
  112. <input type="button" v-if="ok" value="a">
  113. <input type="button" :value="'b'">
  114. </div>
  115. `
  116. }).$mount()
  117. expect(vm.$el.children[0].value).toBe('a')
  118. vm.ok = false
  119. waitForUpdate(() => {
  120. expect(vm.$el.children[0].value).toBe('b')
  121. vm.ok = true
  122. }).then(() => {
  123. expect(vm.$el.children[0].value).toBe('a')
  124. }).then(done)
  125. })
  126. // #6313
  127. it('should not replace node when switching between text-like inputs', done => {
  128. const vm = new Vue({
  129. data: { show: false },
  130. template: `
  131. <div>
  132. <input :type="show ? 'text' : 'password'">
  133. </div>
  134. `
  135. }).$mount()
  136. const node = vm.$el.children[0]
  137. expect(vm.$el.children[0].type).toBe('password')
  138. vm.$el.children[0].value = 'test'
  139. vm.show = true
  140. waitForUpdate(() => {
  141. expect(vm.$el.children[0]).toBe(node)
  142. expect(vm.$el.children[0].value).toBe('test')
  143. expect(vm.$el.children[0].type).toBe('text')
  144. vm.show = false
  145. }).then(() => {
  146. expect(vm.$el.children[0]).toBe(node)
  147. expect(vm.$el.children[0].value).toBe('test')
  148. expect(vm.$el.children[0].type).toBe('password')
  149. }).then(done)
  150. })
  151. it('should properly patch nested HOC when root element is replaced', done => {
  152. const vm = new Vue({
  153. template: `<foo class="hello" ref="foo" />`,
  154. components: {
  155. foo: {
  156. template: `<bar ref="bar" />`,
  157. components: {
  158. bar: {
  159. template: `<div v-if="ok"></div><span v-else></span>`,
  160. data () {
  161. return { ok: true }
  162. }
  163. }
  164. }
  165. }
  166. }
  167. }).$mount()
  168. expect(vm.$refs.foo.$refs.bar.$el.tagName).toBe('DIV')
  169. expect(vm.$refs.foo.$refs.bar.$el.className).toBe(`hello`)
  170. vm.$refs.foo.$refs.bar.ok = false
  171. waitForUpdate(() => {
  172. expect(vm.$refs.foo.$refs.bar.$el.tagName).toBe('SPAN')
  173. expect(vm.$refs.foo.$refs.bar.$el.className).toBe(`hello`)
  174. }).then(done)
  175. })
  176. // #6790
  177. it('should not render undefined for empty nested arrays', () => {
  178. const vm = new Vue({
  179. template: `<div><template v-for="i in emptyArr"></template></div>`,
  180. data: { emptyArr: [] }
  181. }).$mount()
  182. expect(vm.$el.textContent).toBe('')
  183. })
  184. // #6803
  185. it('backwards compat with checkbox code generated before 2.4', () => {
  186. const spy = jasmine.createSpy()
  187. const vm = new Vue({
  188. data: {
  189. label: 'foobar',
  190. name: 'foobar'
  191. },
  192. computed: {
  193. value: {
  194. get () {
  195. return 1
  196. },
  197. set: spy
  198. }
  199. },
  200. render (h) {
  201. const _vm = this
  202. return h('div', {},
  203. [h('input', {
  204. directives: [{
  205. name: 'model',
  206. rawName: 'v-model',
  207. value: (_vm.value),
  208. expression: 'value'
  209. }],
  210. attrs: {
  211. 'type': 'radio',
  212. 'name': _vm.name
  213. },
  214. domProps: {
  215. 'value': _vm.label,
  216. 'checked': _vm._q(_vm.value, _vm.label)
  217. },
  218. on: {
  219. '__c': function ($event) {
  220. _vm.value = _vm.label
  221. }
  222. }
  223. })])
  224. }
  225. }).$mount()
  226. document.body.appendChild(vm.$el)
  227. vm.$el.children[0].click()
  228. expect(spy).toHaveBeenCalled()
  229. })
  230. // #7041
  231. it('transition children with only deep bindings should be patched on update', done => {
  232. const vm = new Vue({
  233. template: `
  234. <div>
  235. <transition>
  236. <div :style="style"></div>
  237. </transition>
  238. </div>
  239. `,
  240. data: () => ({
  241. style: { color: 'red' }
  242. })
  243. }).$mount()
  244. expect(vm.$el.children[0].style.color).toBe('red')
  245. vm.style.color = 'green'
  246. waitForUpdate(() => {
  247. expect(vm.$el.children[0].style.color).toBe('green')
  248. }).then(done)
  249. })
  250. })