model-component.spec.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import Vue from 'vue'
  2. describe('Directive v-model component', () => {
  3. it('should work', done => {
  4. const vm = new Vue({
  5. data: {
  6. msg: 'hello'
  7. },
  8. template: `
  9. <div>
  10. <p>{{ msg }}</p>
  11. <test v-model="msg"></test>
  12. </div>
  13. `,
  14. components: {
  15. test: {
  16. props: ['value'],
  17. template: `<input :value="value" @input="$emit('input', $event.target.value)">`
  18. }
  19. }
  20. }).$mount()
  21. document.body.appendChild(vm.$el)
  22. waitForUpdate(() => {
  23. const input = vm.$el.querySelector('input')
  24. input.value = 'world'
  25. triggerEvent(input, 'input')
  26. })
  27. .then(() => {
  28. expect(vm.msg).toEqual('world')
  29. expect(vm.$el.querySelector('p').textContent).toEqual('world')
  30. vm.msg = 'changed'
  31. })
  32. .then(() => {
  33. expect(vm.$el.querySelector('p').textContent).toEqual('changed')
  34. expect(vm.$el.querySelector('input').value).toEqual('changed')
  35. })
  36. .then(() => {
  37. document.body.removeChild(vm.$el)
  38. })
  39. .then(done)
  40. })
  41. it('should work with native tags with "is"', done => {
  42. const vm = new Vue({
  43. data: {
  44. msg: 'hello'
  45. },
  46. template: `
  47. <div>
  48. <p>{{ msg }}</p>
  49. <input is="test" v-model="msg">
  50. </div>
  51. `,
  52. components: {
  53. test: {
  54. props: ['value'],
  55. template: `<input :value="value" @input="$emit('input', $event.target.value)">`
  56. }
  57. }
  58. }).$mount()
  59. document.body.appendChild(vm.$el)
  60. waitForUpdate(() => {
  61. const input = vm.$el.querySelector('input')
  62. input.value = 'world'
  63. triggerEvent(input, 'input')
  64. })
  65. .then(() => {
  66. expect(vm.msg).toEqual('world')
  67. expect(vm.$el.querySelector('p').textContent).toEqual('world')
  68. vm.msg = 'changed'
  69. })
  70. .then(() => {
  71. expect(vm.$el.querySelector('p').textContent).toEqual('changed')
  72. expect(vm.$el.querySelector('input').value).toEqual('changed')
  73. })
  74. .then(() => {
  75. document.body.removeChild(vm.$el)
  76. })
  77. .then(done)
  78. })
  79. it('should support customization via model option', done => {
  80. const spy = vi.fn()
  81. const vm = new Vue({
  82. data: {
  83. msg: 'hello'
  84. },
  85. methods: {
  86. spy
  87. },
  88. template: `
  89. <div>
  90. <p>{{ msg }}</p>
  91. <test v-model="msg" @update="spy"></test>
  92. </div>
  93. `,
  94. components: {
  95. test: {
  96. model: {
  97. prop: 'currentValue',
  98. event: 'update'
  99. },
  100. props: ['currentValue'],
  101. template: `<input :value="currentValue" @input="$emit('update', $event.target.value)">`
  102. }
  103. }
  104. }).$mount()
  105. document.body.appendChild(vm.$el)
  106. waitForUpdate(() => {
  107. const input = vm.$el.querySelector('input')
  108. input.value = 'world'
  109. triggerEvent(input, 'input')
  110. })
  111. .then(() => {
  112. expect(vm.msg).toEqual('world')
  113. expect(vm.$el.querySelector('p').textContent).toEqual('world')
  114. expect(spy).toHaveBeenCalledWith('world')
  115. vm.msg = 'changed'
  116. })
  117. .then(() => {
  118. expect(vm.$el.querySelector('p').textContent).toEqual('changed')
  119. expect(vm.$el.querySelector('input').value).toEqual('changed')
  120. })
  121. .then(() => {
  122. document.body.removeChild(vm.$el)
  123. })
  124. .then(done)
  125. })
  126. it('modifier: .number', () => {
  127. const vm = new Vue({
  128. template: `<div><my-input ref="input" v-model.number="text"></my-input></div>`,
  129. data: { text: 'foo' },
  130. components: {
  131. 'my-input': {
  132. template: '<input>'
  133. }
  134. }
  135. }).$mount()
  136. expect(vm.text).toBe('foo')
  137. vm.$refs.input.$emit('input', 'bar')
  138. expect(vm.text).toBe('bar')
  139. vm.$refs.input.$emit('input', '123')
  140. expect(vm.text).toBe(123)
  141. })
  142. it('modifier: .trim', () => {
  143. const vm = new Vue({
  144. template: `<div><my-input ref="input" v-model.trim="text"></my-input></div>`,
  145. data: { text: 'foo' },
  146. components: {
  147. 'my-input': {
  148. template: '<input>'
  149. }
  150. }
  151. }).$mount()
  152. expect(vm.text).toBe('foo')
  153. vm.$refs.input.$emit('input', ' bar ')
  154. expect(vm.text).toBe('bar')
  155. vm.$refs.input.$emit('input', ' foo o ')
  156. expect(vm.text).toBe('foo o')
  157. })
  158. // #8436
  159. it('should not double transform mode props', () => {
  160. const BaseInput = {
  161. props: ['value'],
  162. render(h) {
  163. return h('input', {
  164. domProps: {
  165. value: this.value
  166. },
  167. on: {
  168. input: e => this.$emit('input', e.target.value)
  169. }
  170. })
  171. }
  172. }
  173. const FunctionalWrapper = {
  174. functional: true,
  175. render(h, ctx) {
  176. return h(BaseInput, ctx.data)
  177. }
  178. }
  179. let triggerCount = 0
  180. const vm = new Vue({
  181. components: {
  182. FunctionalWrapper
  183. },
  184. template: `
  185. <div>
  186. <functional-wrapper v-model="val"/>
  187. </div>
  188. `,
  189. data: {
  190. internalVal: ''
  191. },
  192. computed: {
  193. val: {
  194. get() {
  195. return this.internalVal
  196. },
  197. set(val) {
  198. triggerCount++
  199. this.internalVal = val
  200. }
  201. }
  202. }
  203. }).$mount()
  204. document.body.appendChild(vm.$el)
  205. triggerEvent(vm.$el.querySelector('input'), 'input')
  206. expect(triggerCount).toBe(1)
  207. document.body.removeChild(vm.$el)
  208. })
  209. // #9330
  210. it('should add value to $attrs if not defined in props', () => {
  211. const TestComponent = {
  212. inheritAttrs: false,
  213. render(h) {
  214. return h('div', this.$attrs.value)
  215. }
  216. }
  217. const vm = new Vue({
  218. components: {
  219. TestComponent
  220. },
  221. template: `
  222. <div>
  223. <test-component v-model="val"/>
  224. </div>
  225. `,
  226. data: {
  227. val: 'foo'
  228. }
  229. }).$mount()
  230. expect(vm.$el.innerHTML).toBe('<div>foo</div>')
  231. })
  232. })