patchProps.spec.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import { patchProp } from '../src/patchProp'
  2. import { render, h } from '../src'
  3. describe('runtime-dom: props patching', () => {
  4. test('basic', () => {
  5. const el = document.createElement('div')
  6. patchProp(el, 'id', null, 'foo')
  7. expect(el.id).toBe('foo')
  8. // prop with string value should be set to empty string on null values
  9. patchProp(el, 'id', null, null)
  10. expect(el.id).toBe('')
  11. expect(el.getAttribute('id')).toBe(null)
  12. })
  13. test('value', () => {
  14. const el = document.createElement('input')
  15. patchProp(el, 'value', null, 'foo')
  16. expect(el.value).toBe('foo')
  17. patchProp(el, 'value', null, null)
  18. expect(el.value).toBe('')
  19. expect(el.getAttribute('value')).toBe(null)
  20. const obj = {}
  21. patchProp(el, 'value', null, obj)
  22. expect(el.value).toBe(obj.toString())
  23. expect((el as any)._value).toBe(obj)
  24. })
  25. // For <input type="text">, setting el.value won't create a `value` attribute
  26. // so we need to add tests for other elements
  27. test('value for non-text input', () => {
  28. const el = document.createElement('option')
  29. el.textContent = 'foo' // #4956
  30. patchProp(el, 'value', null, 'foo')
  31. expect(el.getAttribute('value')).toBe('foo')
  32. expect(el.value).toBe('foo')
  33. patchProp(el, 'value', null, null)
  34. el.textContent = ''
  35. expect(el.value).toBe('')
  36. // #3475
  37. expect(el.getAttribute('value')).toBe(null)
  38. })
  39. test('boolean prop', () => {
  40. const el = document.createElement('select')
  41. patchProp(el, 'multiple', null, '')
  42. expect(el.multiple).toBe(true)
  43. patchProp(el, 'multiple', null, null)
  44. expect(el.multiple).toBe(false)
  45. patchProp(el, 'multiple', null, true)
  46. expect(el.multiple).toBe(true)
  47. patchProp(el, 'multiple', null, 0)
  48. expect(el.multiple).toBe(false)
  49. patchProp(el, 'multiple', null, '0')
  50. expect(el.multiple).toBe(true)
  51. patchProp(el, 'multiple', null, false)
  52. expect(el.multiple).toBe(false)
  53. patchProp(el, 'multiple', null, 1)
  54. expect(el.multiple).toBe(true)
  55. patchProp(el, 'multiple', null, undefined)
  56. expect(el.multiple).toBe(false)
  57. })
  58. test('innerHTML unmount prev children', () => {
  59. const fn = jest.fn()
  60. const comp = {
  61. render: () => 'foo',
  62. unmounted: fn
  63. }
  64. const root = document.createElement('div')
  65. render(h('div', null, [h(comp)]), root)
  66. expect(root.innerHTML).toBe(`<div>foo</div>`)
  67. render(h('div', { innerHTML: 'bar' }), root)
  68. expect(root.innerHTML).toBe(`<div>bar</div>`)
  69. expect(fn).toHaveBeenCalled()
  70. })
  71. // #954
  72. test('(svg) innerHTML unmount prev children', () => {
  73. const fn = jest.fn()
  74. const comp = {
  75. render: () => 'foo',
  76. unmounted: fn
  77. }
  78. const root = document.createElement('div')
  79. render(h('div', null, [h(comp)]), root)
  80. expect(root.innerHTML).toBe(`<div>foo</div>`)
  81. render(h('svg', { innerHTML: '<g></g>' }), root)
  82. expect(root.innerHTML).toBe(`<svg><g></g></svg>`)
  83. expect(fn).toHaveBeenCalled()
  84. })
  85. test('textContent unmount prev children', () => {
  86. const fn = jest.fn()
  87. const comp = {
  88. render: () => 'foo',
  89. unmounted: fn
  90. }
  91. const root = document.createElement('div')
  92. render(h('div', null, [h(comp)]), root)
  93. expect(root.innerHTML).toBe(`<div>foo</div>`)
  94. render(h('div', { textContent: 'bar' }), root)
  95. expect(root.innerHTML).toBe(`<div>bar</div>`)
  96. expect(fn).toHaveBeenCalled()
  97. })
  98. // #1049
  99. test('set value as-is for non string-value props', () => {
  100. const el = document.createElement('video')
  101. // jsdom doesn't really support video playback. srcObject in a real browser
  102. // should default to `null`, but in jsdom it's `undefined`.
  103. // anyway, here we just want to make sure Vue doesn't set non-string props
  104. // to an empty string on nullish values - it should reset to its default
  105. // value.
  106. const initialValue = el.srcObject
  107. const fakeObject = {}
  108. patchProp(el, 'srcObject', null, fakeObject)
  109. expect(el.srcObject).not.toBe(fakeObject)
  110. patchProp(el, 'srcObject', null, null)
  111. expect(el.srcObject).toBe(initialValue)
  112. })
  113. test('catch and warn prop set TypeError', () => {
  114. const el = document.createElement('div')
  115. Object.defineProperty(el, 'someProp', {
  116. set() {
  117. throw new TypeError('Invalid type')
  118. }
  119. })
  120. patchProp(el, 'someProp', null, 'foo')
  121. expect(`Failed setting prop "someProp" on <div>`).toHaveBeenWarnedLast()
  122. })
  123. // #1576
  124. test('remove attribute when value is falsy', () => {
  125. const el = document.createElement('div')
  126. patchProp(el, 'id', null, '')
  127. expect(el.hasAttribute('id')).toBe(true)
  128. patchProp(el, 'id', null, null)
  129. expect(el.hasAttribute('id')).toBe(false)
  130. patchProp(el, 'id', null, '')
  131. expect(el.hasAttribute('id')).toBe(true)
  132. patchProp(el, 'id', null, undefined)
  133. expect(el.hasAttribute('id')).toBe(false)
  134. patchProp(el, 'id', null, '')
  135. expect(el.hasAttribute('id')).toBe(true)
  136. // #2677
  137. const img = document.createElement('img')
  138. patchProp(img, 'width', null, '')
  139. expect(el.hasAttribute('width')).toBe(false)
  140. patchProp(img, 'width', null, 0)
  141. expect(img.hasAttribute('width')).toBe(true)
  142. patchProp(img, 'width', null, null)
  143. expect(img.hasAttribute('width')).toBe(false)
  144. patchProp(img, 'width', null, 0)
  145. expect(img.hasAttribute('width')).toBe(true)
  146. patchProp(img, 'width', null, undefined)
  147. expect(img.hasAttribute('width')).toBe(false)
  148. patchProp(img, 'width', null, 0)
  149. expect(img.hasAttribute('width')).toBe(true)
  150. })
  151. test('form attribute', () => {
  152. const el = document.createElement('input')
  153. patchProp(el, 'form', null, 'foo')
  154. // non existant element
  155. expect(el.form).toBe(null)
  156. expect(el.getAttribute('form')).toBe('foo')
  157. // remove attribute
  158. patchProp(el, 'form', 'foo', null)
  159. expect(el.getAttribute('form')).toBe(null)
  160. })
  161. test('readonly type prop on textarea', () => {
  162. const el = document.createElement('textarea')
  163. // just to verify that it doesn't throw when i.e. switching a dynamic :is from an 'input' to a 'textarea'
  164. // see https://github.com/vuejs/vue-next/issues/2766
  165. patchProp(el, 'type', 'text', null)
  166. })
  167. test('force patch as prop', () => {
  168. const el = document.createElement('div') as any
  169. patchProp(el, '.x', null, 1)
  170. expect(el.x).toBe(1)
  171. })
  172. test('force patch as attribute', () => {
  173. const el = document.createElement('div') as any
  174. el.x = 1
  175. patchProp(el, '^x', null, 2)
  176. expect(el.x).toBe(1)
  177. expect(el.getAttribute('x')).toBe('2')
  178. })
  179. test('input with size', () => {
  180. const el = document.createElement('input')
  181. patchProp(el, 'size', null, 100)
  182. expect(el.size).toBe(100)
  183. patchProp(el, 'size', 100, null)
  184. expect(el.getAttribute('size')).toBe(null)
  185. })
  186. test('patch value for select', () => {
  187. const root = document.createElement('div')
  188. render(
  189. h('select', { value: 'foo' }, [
  190. h('option', { value: 'foo' }, 'foo'),
  191. h('option', { value: 'bar' }, 'bar')
  192. ]),
  193. root
  194. )
  195. const el = root.children[0] as HTMLSelectElement
  196. expect(el.value).toBe('foo')
  197. render(
  198. h('select', { value: 'baz' }, [
  199. h('option', { value: 'foo' }, 'foo'),
  200. h('option', { value: 'baz' }, 'baz')
  201. ]),
  202. root
  203. )
  204. expect(el.value).toBe('baz')
  205. })
  206. })