model_spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. var _ = require('../../../../src/util')
  2. var Vue = require('../../../../src/vue')
  3. /**
  4. * Mock event helper
  5. */
  6. function trigger (target, event, process) {
  7. var e = document.createEvent('HTMLEvents')
  8. e.initEvent(event, true, true)
  9. if (process) process(e)
  10. target.dispatchEvent(e)
  11. }
  12. /**
  13. * setting <select>'s value in IE9 doesn't work
  14. * we have to manually loop through the options
  15. */
  16. function updateSelect (el, value) {
  17. /* jshint eqeqeq: false */
  18. var options = el.options
  19. var i = options.length
  20. while (i--) {
  21. if (options[i].value == value) {
  22. options[i].selected = true
  23. break
  24. }
  25. }
  26. }
  27. if (_.inBrowser) {
  28. describe('v-model', function () {
  29. var el
  30. beforeEach(function () {
  31. el = document.createElement('div')
  32. el.style.display = 'none'
  33. document.body.appendChild(el)
  34. spyOn(_, 'warn')
  35. })
  36. afterEach(function () {
  37. document.body.removeChild(el)
  38. })
  39. it('radio buttons', function (done) {
  40. var vm = new Vue({
  41. el: el,
  42. data: {
  43. test: 'a'
  44. },
  45. template:
  46. '<input type="radio" value="a" v-model="test" name="test">' +
  47. '<input type="radio" value="b" v-model="test" name="test">'
  48. })
  49. expect(el.childNodes[0].checked).toBe(true)
  50. expect(el.childNodes[1].checked).toBe(false)
  51. vm.test = 'b'
  52. _.nextTick(function () {
  53. expect(el.childNodes[0].checked).toBe(false)
  54. expect(el.childNodes[1].checked).toBe(true)
  55. el.childNodes[0].click()
  56. expect(el.childNodes[0].checked).toBe(true)
  57. expect(el.childNodes[1].checked).toBe(false)
  58. expect(vm.test).toBe('a')
  59. vm._directives[1].unbind()
  60. el.childNodes[1].click()
  61. expect(vm.test).toBe('a')
  62. done()
  63. })
  64. })
  65. it('radio default value', function () {
  66. var vm = new Vue({
  67. el: el,
  68. data: {},
  69. template: '<input type="radio" checked value="a" v-model="test">'
  70. })
  71. expect(vm.test).toBe('a')
  72. })
  73. it('checkbox', function (done) {
  74. var vm = new Vue({
  75. el: el,
  76. data: {
  77. test: true
  78. },
  79. template: '<input type="checkbox" v-model="test">'
  80. })
  81. expect(el.firstChild.checked).toBe(true)
  82. vm.test = false
  83. _.nextTick(function () {
  84. expect(el.firstChild.checked).toBe(false)
  85. expect(vm.test).toBe(false)
  86. el.firstChild.click()
  87. expect(el.firstChild.checked).toBe(true)
  88. expect(vm.test).toBe(true)
  89. vm._directives[0].unbind()
  90. el.firstChild.click()
  91. expect(el.firstChild.checked).toBe(false)
  92. expect(vm.test).toBe(true)
  93. done()
  94. })
  95. })
  96. it('checkbox default value', function () {
  97. var vm = new Vue({
  98. el: el,
  99. data: {},
  100. template: '<input type="checkbox" checked v-model="test">'
  101. })
  102. expect(vm.test).toBe(true)
  103. })
  104. it('select', function (done) {
  105. var vm = new Vue({
  106. el: el,
  107. data: {
  108. test: 'b'
  109. },
  110. template:
  111. '<select v-model="test">' +
  112. '<option>a</option>' +
  113. '<option>b</option>' +
  114. '<option>c</option>' +
  115. '</select>'
  116. })
  117. expect(vm.test).toBe('b')
  118. expect(el.firstChild.value).toBe('b')
  119. expect(el.firstChild.childNodes[1].selected).toBe(true)
  120. vm.test = 'c'
  121. _.nextTick(function () {
  122. expect(el.firstChild.value).toBe('c')
  123. expect(el.firstChild.childNodes[2].selected).toBe(true)
  124. updateSelect(el.firstChild, 'a')
  125. trigger(el.firstChild, 'change')
  126. expect(vm.test).toBe('a')
  127. done()
  128. })
  129. })
  130. it('select default value', function () {
  131. var vm = new Vue({
  132. el: el,
  133. data: {
  134. test: 'a'
  135. },
  136. template:
  137. '<select v-model="test">' +
  138. '<option>a</option>' +
  139. '<option selected>b</option>' +
  140. '</select>'
  141. })
  142. expect(vm.test).toBe('b')
  143. expect(el.firstChild.value).toBe('b')
  144. expect(el.firstChild.childNodes[1].selected).toBe(true)
  145. })
  146. it('select + multiple', function (done) {
  147. var vm = new Vue({
  148. el: el,
  149. data: {
  150. test: [2] // test number soft equal
  151. },
  152. template:
  153. '<select v-model="test" multiple>' +
  154. '<option>1</option>' +
  155. '<option>2</option>' +
  156. '<option>3</option>' +
  157. '</select>'
  158. })
  159. var opts = el.firstChild.options
  160. expect(opts[0].selected).toBe(false)
  161. expect(opts[1].selected).toBe(true)
  162. expect(opts[2].selected).toBe(false)
  163. vm.test = [1, '3'] // mix of number/string
  164. _.nextTick(function () {
  165. expect(opts[0].selected).toBe(true)
  166. expect(opts[1].selected).toBe(false)
  167. expect(opts[2].selected).toBe(true)
  168. opts[0].selected = false
  169. opts[1].selected = true
  170. trigger(el.firstChild, 'change')
  171. expect(vm.test[0]).toBe('2')
  172. expect(vm.test[1]).toBe('3')
  173. done()
  174. })
  175. })
  176. it('select + multiple default value', function () {
  177. var vm = new Vue({
  178. el: el,
  179. data: {},
  180. template:
  181. '<select v-model="test" multiple>' +
  182. '<option>a</option>' +
  183. '<option selected>b</option>' +
  184. '<option selected>c</option>' +
  185. '</select>'
  186. })
  187. expect(vm.test[0]).toBe('b')
  188. expect(vm.test[1]).toBe('c')
  189. })
  190. it('select + options', function (done) {
  191. var vm = new Vue({
  192. el: el,
  193. data: {
  194. test: 'b',
  195. opts: ['a', 'b', 'c']
  196. },
  197. template: '<select v-model="test" options="opts"></select>'
  198. })
  199. var opts = el.firstChild.options
  200. expect(opts.length).toBe(3)
  201. expect(opts[0].selected).toBe(false)
  202. expect(opts[1].selected).toBe(true)
  203. expect(opts[2].selected).toBe(false)
  204. vm.opts = ['b', 'c']
  205. _.nextTick(function () {
  206. expect(opts.length).toBe(2)
  207. expect(opts[0].selected).toBe(true)
  208. expect(opts[1].selected).toBe(false)
  209. // should teardown option watcher when unbind
  210. expect(vm._watcherList.length).toBe(2)
  211. vm._directives[0].unbind()
  212. expect(vm._watcherList.length).toBe(0)
  213. done()
  214. })
  215. })
  216. it('select + options + text', function () {
  217. var vm = new Vue({
  218. el: el,
  219. data: {
  220. test: 'b',
  221. opts: [
  222. { text: 'A', value: 'a' },
  223. { text: 'B', value: 'b' }
  224. ]
  225. },
  226. template: '<select v-model="test" options="opts"></select>'
  227. })
  228. expect(el.firstChild.innerHTML).toBe(
  229. '<option value="a">A</option>' +
  230. '<option value="b">B</option>'
  231. )
  232. var opts = el.firstChild.options
  233. expect(opts[0].selected).toBe(false)
  234. expect(opts[1].selected).toBe(true)
  235. })
  236. it('select + options + optgroup', function () {
  237. var vm = new Vue({
  238. el: el,
  239. data: {
  240. test: 'b',
  241. opts: [
  242. { label: 'A', options: ['a','b'] },
  243. { label: 'B', options: ['c'] }
  244. ]
  245. },
  246. template: '<select v-model="test" options="opts"></select>'
  247. })
  248. expect(el.firstChild.innerHTML).toBe(
  249. '<optgroup label="A">' +
  250. '<option value="a">a</option><option value="b">b</option>' +
  251. '</optgroup>' +
  252. '<optgroup label="B">' +
  253. '<option value="c">c</option>' +
  254. '</optgroup>'
  255. )
  256. var opts = el.firstChild.options
  257. expect(opts[0].selected).toBe(false)
  258. expect(opts[1].selected).toBe(true)
  259. expect(opts[2].selected).toBe(false)
  260. })
  261. it('text', function (done) {
  262. var vm = new Vue({
  263. el: el,
  264. data: {
  265. test: 'b'
  266. },
  267. template: '<input v-model="test">'
  268. })
  269. expect(el.firstChild.value).toBe('b')
  270. vm.test = 'a'
  271. _.nextTick(function () {
  272. expect(el.firstChild.value).toBe('a')
  273. el.firstChild.value = 'c'
  274. trigger(el.firstChild, 'input')
  275. expect(vm.test).toBe('c')
  276. vm._directives[0].unbind()
  277. el.firstChild.value = 'd'
  278. trigger(el.firstChild, 'input')
  279. expect(vm.test).toBe('c')
  280. done()
  281. })
  282. })
  283. it('text default value', function () {
  284. var vm = new Vue({
  285. el: el,
  286. data: {
  287. test: 'b'
  288. },
  289. template: '<input v-model="test" value="a">'
  290. })
  291. expect(vm.test).toBe('a')
  292. expect(el.firstChild.value).toBe('a')
  293. })
  294. it('text lazy', function () {
  295. var vm = new Vue({
  296. el: el,
  297. data: {
  298. test: 'b'
  299. },
  300. template: '<input v-model="test" lazy>'
  301. })
  302. expect(el.firstChild.value).toBe('b')
  303. expect(vm.test).toBe('b')
  304. el.firstChild.value = 'c'
  305. trigger(el.firstChild, 'input')
  306. expect(vm.test).toBe('b')
  307. trigger(el.firstChild, 'change')
  308. expect(vm.test).toBe('c')
  309. })
  310. it('text with filters', function (done) {
  311. var vm = new Vue({
  312. el: el,
  313. data: {
  314. test: 'b'
  315. },
  316. filters: {
  317. test: {
  318. write: function (val) {
  319. return val.toUpperCase()
  320. }
  321. }
  322. },
  323. template: '<input v-model="test | uppercase | test">'
  324. })
  325. expect(el.firstChild.value).toBe('B')
  326. el.firstChild.value = 'cc'
  327. trigger(el.firstChild, 'input')
  328. _.nextTick(function () {
  329. expect(el.firstChild.value).toBe('CC')
  330. expect(vm.test).toBe('CC')
  331. done()
  332. })
  333. })
  334. it('number', function () {
  335. var vm = new Vue({
  336. el: el,
  337. data: {
  338. test: 1
  339. },
  340. template: '<input v-model="test" number>'
  341. })
  342. el.firstChild.value = 2
  343. trigger(el.firstChild, 'input')
  344. expect(vm.test).toBe(2)
  345. })
  346. it('IE9 cut and delete', function (done) {
  347. var ie9 = _.isIE9
  348. _.isIE9 = true
  349. var vm = new Vue({
  350. el: el,
  351. data: {
  352. test: 'aaa'
  353. },
  354. template: '<input v-model="test">'
  355. })
  356. var input = el.firstChild
  357. input.value = 'aa'
  358. trigger(input, 'cut')
  359. _.nextTick(function () {
  360. expect(vm.test).toBe('aa')
  361. input.value = 'a'
  362. trigger(input, 'keyup', function (e) {
  363. e.keyCode = 8
  364. })
  365. expect(vm.test).toBe('a')
  366. // teardown
  367. vm._directives[0].unbind()
  368. input.value = 'bbb'
  369. trigger(input, 'keyup', function (e) {
  370. e.keyCode = 8
  371. })
  372. expect(vm.test).toBe('a')
  373. _.isIE9 = ie9
  374. done()
  375. })
  376. })
  377. it('text + compositionevents', function (done) {
  378. var vm = new Vue({
  379. el: el,
  380. data: {
  381. test: 'aaa',
  382. test2: 'bbb'
  383. },
  384. template: '<input v-model="test"><input v-model="test2 | uppsercase">'
  385. })
  386. var input = el.firstChild
  387. var input2 = el.childNodes[1]
  388. trigger(input, 'compositionstart')
  389. trigger(input2, 'compositionstart')
  390. input.value = input2.value = 'ccc'
  391. // input before composition unlock should not call set
  392. trigger(input, 'input')
  393. trigger(input2, 'input')
  394. expect(vm.test).toBe('aaa')
  395. expect(vm.test2).toBe('bbb')
  396. // after composition unlock it should work
  397. trigger(input, 'compositionend')
  398. trigger(input2, 'compositionend')
  399. trigger(input, 'input')
  400. trigger(input2, 'input')
  401. expect(vm.test).toBe('ccc')
  402. expect(vm.test2).toBe('ccc')
  403. // IE complains about "unspecified error" when calling
  404. // setSelectionRange() on an input element that's been
  405. // removed from the DOM, so we wait until the
  406. // selection range callback has fired to end this test.
  407. _.nextTick(done)
  408. })
  409. it('textarea', function () {
  410. var vm = new Vue({
  411. el: el,
  412. data: {
  413. test: 'b',
  414. b: 'BB'
  415. },
  416. template: '<textarea v-model="test">a {{b}} c</textarea>'
  417. })
  418. expect(vm.test).toBe('a BB c')
  419. expect(el.firstChild.value).toBe('a BB c')
  420. })
  421. it('warn invalid tag', function () {
  422. var vm = new Vue({
  423. el: el,
  424. template: '<div v-model="test"></div>'
  425. })
  426. expect(_.warn).toHaveBeenCalled()
  427. })
  428. it('warn invalid option value', function () {
  429. var vm = new Vue({
  430. el: el,
  431. data: { a: 123 },
  432. template: '<select v-model="test" options="a"></select>'
  433. })
  434. expect(_.warn).toHaveBeenCalled()
  435. })
  436. })
  437. }