model_spec.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. var _ = require('../../../../src/util')
  2. var Vue = require('../../../../src/vue')
  3. // unset jQuery to bypass jQuery check for normal test cases
  4. jQuery = null
  5. /**
  6. * Mock event helper
  7. */
  8. function trigger (target, event, process) {
  9. var e = document.createEvent('HTMLEvents')
  10. e.initEvent(event, true, true)
  11. if (process) process(e)
  12. target.dispatchEvent(e)
  13. }
  14. /**
  15. * setting <select>'s value in IE9 doesn't work
  16. * we have to manually loop through the options
  17. */
  18. function updateSelect (el, value) {
  19. /* jshint eqeqeq: false */
  20. var options = el.options
  21. var i = options.length
  22. while (i--) {
  23. if (options[i].value == value) {
  24. options[i].selected = true
  25. break
  26. }
  27. }
  28. }
  29. if (_.inBrowser) {
  30. describe('v-model', function () {
  31. var el
  32. beforeEach(function () {
  33. el = document.createElement('div')
  34. el.style.display = 'none'
  35. document.body.appendChild(el)
  36. spyOn(_, 'warn')
  37. })
  38. it('radio buttons', function (done) {
  39. var vm = new Vue({
  40. el: el,
  41. data: {
  42. test: 'a'
  43. },
  44. template:
  45. '<input type="radio" value="a" v-model="test" name="test">' +
  46. '<input type="radio" value="b" v-model="test" name="test">'
  47. })
  48. expect(el.childNodes[0].checked).toBe(true)
  49. expect(el.childNodes[1].checked).toBe(false)
  50. vm.test = 'b'
  51. _.nextTick(function () {
  52. expect(el.childNodes[0].checked).toBe(false)
  53. expect(el.childNodes[1].checked).toBe(true)
  54. el.childNodes[0].click()
  55. expect(el.childNodes[0].checked).toBe(true)
  56. expect(el.childNodes[1].checked).toBe(false)
  57. expect(vm.test).toBe('a')
  58. vm._directives[1].unbind()
  59. el.childNodes[1].click()
  60. expect(vm.test).toBe('a')
  61. done()
  62. })
  63. })
  64. it('radio default value', function () {
  65. var vm = new Vue({
  66. el: el,
  67. data: {},
  68. template: '<input type="radio" checked value="a" v-model="test">'
  69. })
  70. expect(vm.test).toBe('a')
  71. })
  72. it('checkbox', function (done) {
  73. var vm = new Vue({
  74. el: el,
  75. data: {
  76. test: true
  77. },
  78. template: '<input type="checkbox" v-model="test">'
  79. })
  80. expect(el.firstChild.checked).toBe(true)
  81. vm.test = false
  82. _.nextTick(function () {
  83. expect(el.firstChild.checked).toBe(false)
  84. expect(vm.test).toBe(false)
  85. el.firstChild.click()
  86. expect(el.firstChild.checked).toBe(true)
  87. expect(vm.test).toBe(true)
  88. vm._directives[0].unbind()
  89. el.firstChild.click()
  90. expect(el.firstChild.checked).toBe(false)
  91. expect(vm.test).toBe(true)
  92. done()
  93. })
  94. })
  95. it('checkbox default value', function () {
  96. var vm = new Vue({
  97. el: el,
  98. data: {},
  99. template: '<input type="checkbox" checked v-model="test">'
  100. })
  101. expect(vm.test).toBe(true)
  102. })
  103. it('select', function (done) {
  104. var vm = new Vue({
  105. el: el,
  106. data: {
  107. test: 'b'
  108. },
  109. template:
  110. '<select v-model="test">' +
  111. '<option>a</option>' +
  112. '<option>b</option>' +
  113. '<option>c</option>' +
  114. '</select>'
  115. })
  116. expect(vm.test).toBe('b')
  117. expect(el.firstChild.value).toBe('b')
  118. expect(el.firstChild.childNodes[1].selected).toBe(true)
  119. vm.test = 'c'
  120. _.nextTick(function () {
  121. expect(el.firstChild.value).toBe('c')
  122. expect(el.firstChild.childNodes[2].selected).toBe(true)
  123. updateSelect(el.firstChild, 'a')
  124. trigger(el.firstChild, 'change')
  125. expect(vm.test).toBe('a')
  126. done()
  127. })
  128. })
  129. it('select default value', function () {
  130. var vm = new Vue({
  131. el: el,
  132. data: {
  133. test: 'a'
  134. },
  135. template:
  136. '<select v-model="test">' +
  137. '<option>a</option>' +
  138. '<option selected>b</option>' +
  139. '</select>'
  140. })
  141. expect(vm.test).toBe('b')
  142. expect(el.firstChild.value).toBe('b')
  143. expect(el.firstChild.childNodes[1].selected).toBe(true)
  144. })
  145. it('select + empty default value', function () {
  146. var vm = new Vue({
  147. el: el,
  148. template: '<select v-model="test"><option value="" selected>null</option><<option value="1">1</option></select>'
  149. })
  150. expect(vm.test).toBe('')
  151. trigger(vm.$el.firstChild, 'change')
  152. expect(vm.test).toBe('')
  153. })
  154. it('select + multiple', function (done) {
  155. var vm = new Vue({
  156. el: el,
  157. data: {
  158. test: [2] // test number soft equal
  159. },
  160. template:
  161. '<select v-model="test" multiple>' +
  162. '<option>1</option>' +
  163. '<option>2</option>' +
  164. '<option>3</option>' +
  165. '</select>'
  166. })
  167. var opts = el.firstChild.options
  168. expect(opts[0].selected).toBe(false)
  169. expect(opts[1].selected).toBe(true)
  170. expect(opts[2].selected).toBe(false)
  171. vm.test = [1, '3'] // mix of number/string
  172. _.nextTick(function () {
  173. expect(opts[0].selected).toBe(true)
  174. expect(opts[1].selected).toBe(false)
  175. expect(opts[2].selected).toBe(true)
  176. opts[0].selected = false
  177. opts[1].selected = true
  178. trigger(el.firstChild, 'change')
  179. expect(vm.test[0]).toBe('2')
  180. expect(vm.test[1]).toBe('3')
  181. done()
  182. })
  183. })
  184. it('select + multiple default value', function () {
  185. var vm = new Vue({
  186. el: el,
  187. data: {},
  188. template:
  189. '<select v-model="test" multiple>' +
  190. '<option>a</option>' +
  191. '<option selected>b</option>' +
  192. '<option selected>c</option>' +
  193. '</select>'
  194. })
  195. expect(vm.test[0]).toBe('b')
  196. expect(vm.test[1]).toBe('c')
  197. })
  198. it('select + options', function (done) {
  199. var vm = new Vue({
  200. el: el,
  201. data: {
  202. test: 'b',
  203. opts: ['a', 'b', 'c']
  204. },
  205. template: '<select v-model="test" options="opts"></select>'
  206. })
  207. var opts = el.firstChild.options
  208. expect(opts.length).toBe(3)
  209. expect(opts[0].selected).toBe(false)
  210. expect(opts[1].selected).toBe(true)
  211. expect(opts[2].selected).toBe(false)
  212. vm.opts = ['b', 'c']
  213. _.nextTick(function () {
  214. expect(opts.length).toBe(2)
  215. expect(opts[0].selected).toBe(true)
  216. expect(opts[1].selected).toBe(false)
  217. // should teardown option watcher when unbind
  218. expect(vm._watcherList.length).toBe(2)
  219. vm._directives[0]._teardown()
  220. expect(vm._watcherList.length).toBe(0)
  221. done()
  222. })
  223. })
  224. it('select + options + text', function () {
  225. var vm = new Vue({
  226. el: el,
  227. data: {
  228. test: 'b',
  229. opts: [
  230. { text: 'A', value: 'a' },
  231. { text: 'B', value: 'b' }
  232. ]
  233. },
  234. template: '<select v-model="test" options="opts"></select>'
  235. })
  236. expect(el.firstChild.innerHTML).toBe(
  237. '<option value="a">A</option>' +
  238. '<option value="b">B</option>'
  239. )
  240. var opts = el.firstChild.options
  241. expect(opts[0].selected).toBe(false)
  242. expect(opts[1].selected).toBe(true)
  243. })
  244. it('select + options + optgroup', function () {
  245. var vm = new Vue({
  246. el: el,
  247. data: {
  248. test: 'b',
  249. opts: [
  250. { label: 'A', options: ['a','b'] },
  251. { label: 'B', options: ['c'] }
  252. ]
  253. },
  254. template: '<select v-model="test" options="opts"></select>'
  255. })
  256. expect(el.firstChild.innerHTML).toBe(
  257. '<optgroup label="A">' +
  258. '<option value="a">a</option><option value="b">b</option>' +
  259. '</optgroup>' +
  260. '<optgroup label="B">' +
  261. '<option value="c">c</option>' +
  262. '</optgroup>'
  263. )
  264. var opts = el.firstChild.options
  265. expect(opts[0].selected).toBe(false)
  266. expect(opts[1].selected).toBe(true)
  267. expect(opts[2].selected).toBe(false)
  268. })
  269. it('select + number', function () {
  270. var vm = new Vue({
  271. el: el,
  272. data: {
  273. test: '1'
  274. },
  275. template: '<select v-model="test" number><option value="1">1</option></select>'
  276. })
  277. expect(vm.test).toBe('1')
  278. trigger(vm.$el.firstChild, 'change')
  279. expect(vm.test).toBe(1)
  280. })
  281. it('select + number + multiple', function () {
  282. var vm = new Vue({
  283. el: el,
  284. data: {
  285. test: []
  286. },
  287. template: '<select v-model="test" multiple number><option>1</option><option>2</option></select>'
  288. })
  289. ;[].forEach.call(el.querySelectorAll('option'), function (o) {
  290. o.selected = true
  291. })
  292. trigger(el.firstChild, 'change')
  293. expect(vm.test[0]).toBe(1)
  294. expect(vm.test[1]).toBe(2)
  295. })
  296. it('select + number initial value', function () {
  297. var vm = new Vue({
  298. el: el,
  299. data: {
  300. test: '1'
  301. },
  302. template: '<select v-model="test" number><option value="1" selected>1</option></select>'
  303. })
  304. expect(vm.test).toBe(1)
  305. })
  306. it('select + options + filter', function () {
  307. var vm = new Vue({
  308. el: el,
  309. data: {
  310. opts: ['a','b']
  311. },
  312. filters: {
  313. aFilter: function (opts){
  314. return opts.map(function (val,i){
  315. return val + i
  316. })
  317. }
  318. },
  319. template: '<select v-model="test" options="opts | aFilter"></select>'
  320. })
  321. expect(el.firstChild.innerHTML).toBe(
  322. '<option value="a0">a0</option>' +
  323. '<option value="b1">b1</option>'
  324. )
  325. })
  326. it('text', function (done) {
  327. var vm = new Vue({
  328. el: el,
  329. data: {
  330. test: 'b'
  331. },
  332. template: '<input v-model="test">'
  333. })
  334. expect(el.firstChild.value).toBe('b')
  335. vm.test = 'a'
  336. _.nextTick(function () {
  337. expect(el.firstChild.value).toBe('a')
  338. el.firstChild.value = 'c'
  339. trigger(el.firstChild, 'input')
  340. expect(vm.test).toBe('c')
  341. vm._directives[0].unbind()
  342. el.firstChild.value = 'd'
  343. trigger(el.firstChild, 'input')
  344. expect(vm.test).toBe('c')
  345. done()
  346. })
  347. })
  348. it('text default value', function () {
  349. var vm = new Vue({
  350. el: el,
  351. data: {
  352. test: 'b'
  353. },
  354. template: '<input v-model="test | test" value="a">',
  355. filters: {
  356. test: {
  357. read: function (v) {
  358. return v.slice(0, -1)
  359. },
  360. write: function (v) {
  361. return v + 'c'
  362. }
  363. }
  364. }
  365. })
  366. expect(vm.test).toBe('ac')
  367. expect(el.firstChild.value).toBe('a')
  368. })
  369. it('text lazy', function () {
  370. var vm = new Vue({
  371. el: el,
  372. data: {
  373. test: 'b'
  374. },
  375. template: '<input v-model="test" lazy>'
  376. })
  377. expect(el.firstChild.value).toBe('b')
  378. expect(vm.test).toBe('b')
  379. el.firstChild.value = 'c'
  380. trigger(el.firstChild, 'input')
  381. expect(vm.test).toBe('b')
  382. trigger(el.firstChild, 'change')
  383. expect(vm.test).toBe('c')
  384. })
  385. it('text with filters', function (done) {
  386. var vm = new Vue({
  387. el: el,
  388. data: {
  389. test: 'b'
  390. },
  391. filters: {
  392. test: {
  393. write: function (val) {
  394. return val.toLowerCase()
  395. }
  396. }
  397. },
  398. template: '<input v-model="test | uppercase | test">'
  399. })
  400. expect(el.firstChild.value).toBe('B')
  401. el.firstChild.value = 'cc'
  402. trigger(el.firstChild, 'input')
  403. _.nextTick(function () {
  404. expect(el.firstChild.value).toBe('CC')
  405. expect(vm.test).toBe('cc')
  406. done()
  407. })
  408. })
  409. // when there's only write filter, should allow
  410. // out of sync between the input field and actual data
  411. it('text with only write filter', function (done) {
  412. var vm = new Vue({
  413. el: el,
  414. data: {
  415. test: 'b'
  416. },
  417. filters: {
  418. test: {
  419. write: function (val) {
  420. return val.toUpperCase()
  421. }
  422. }
  423. },
  424. template: '<input v-model="test | test">'
  425. })
  426. el.firstChild.value = 'cc'
  427. trigger(el.firstChild, 'input')
  428. _.nextTick(function () {
  429. expect(el.firstChild.value).toBe('cc')
  430. expect(vm.test).toBe('CC')
  431. done()
  432. })
  433. })
  434. it('number', function () {
  435. var vm = new Vue({
  436. el: el,
  437. data: {
  438. test: 1
  439. },
  440. template: '<input v-model="test" value="2" number>'
  441. })
  442. expect(vm.test).toBe(2)
  443. el.firstChild.value = 3
  444. trigger(el.firstChild, 'input')
  445. expect(vm.test).toBe(3)
  446. })
  447. it('IE9 cut and delete', function (done) {
  448. var ie9 = _.isIE9
  449. _.isIE9 = true
  450. var vm = new Vue({
  451. el: el,
  452. data: {
  453. test: 'aaa'
  454. },
  455. template: '<input v-model="test">'
  456. })
  457. var input = el.firstChild
  458. input.value = 'aa'
  459. trigger(input, 'cut')
  460. _.nextTick(function () {
  461. expect(vm.test).toBe('aa')
  462. input.value = 'a'
  463. trigger(input, 'keyup', function (e) {
  464. e.keyCode = 8
  465. })
  466. expect(vm.test).toBe('a')
  467. // teardown
  468. vm._directives[0].unbind()
  469. input.value = 'bbb'
  470. trigger(input, 'keyup', function (e) {
  471. e.keyCode = 8
  472. })
  473. expect(vm.test).toBe('a')
  474. _.isIE9 = ie9
  475. done()
  476. })
  477. })
  478. it('text + compositionevents', function (done) {
  479. var vm = new Vue({
  480. el: el,
  481. data: {
  482. test: 'aaa',
  483. test2: 'bbb'
  484. },
  485. template: '<input v-model="test"><input v-model="test2 | uppercase">'
  486. })
  487. var input = el.firstChild
  488. var input2 = el.childNodes[1]
  489. trigger(input, 'compositionstart')
  490. trigger(input2, 'compositionstart')
  491. input.value = input2.value = 'ccc'
  492. // input before composition unlock should not call set
  493. trigger(input, 'input')
  494. trigger(input2, 'input')
  495. expect(vm.test).toBe('aaa')
  496. expect(vm.test2).toBe('bbb')
  497. // after composition unlock it should work
  498. trigger(input, 'compositionend')
  499. trigger(input2, 'compositionend')
  500. trigger(input, 'input')
  501. trigger(input2, 'input')
  502. expect(vm.test).toBe('ccc')
  503. expect(vm.test2).toBe('ccc')
  504. // IE complains about "unspecified error" when calling
  505. // setSelectionRange() on an input element that's been
  506. // removed from the DOM, so we wait until the
  507. // selection range callback has fired to end this test.
  508. _.nextTick(done)
  509. })
  510. it('textarea', function () {
  511. var vm = new Vue({
  512. el: el,
  513. data: {
  514. test: 'b',
  515. b: 'BB'
  516. },
  517. template: '<textarea v-model="test">a {{b}} c</textarea>'
  518. })
  519. expect(vm.test).toBe('a BB c')
  520. expect(el.firstChild.value).toBe('a BB c')
  521. })
  522. it('warn invalid tag', function () {
  523. var vm = new Vue({
  524. el: el,
  525. template: '<div v-model="test"></div>'
  526. })
  527. expect(_.warn).toHaveBeenCalled()
  528. })
  529. it('warn invalid option value', function () {
  530. var vm = new Vue({
  531. el: el,
  532. data: { a: 123 },
  533. template: '<select v-model="test" options="a"></select>'
  534. })
  535. expect(_.warn).toHaveBeenCalled()
  536. })
  537. it('warn read-only filters', function () {
  538. var vm = new Vue({
  539. el: el,
  540. template: '<input v-model="abc | test">',
  541. filters: {
  542. test: function (v) {
  543. return v
  544. }
  545. }
  546. })
  547. expect(_.warn).toHaveBeenCalled()
  548. })
  549. it('support jQuery change event', function (done) {
  550. // restore jQuery
  551. jQuery = $
  552. var vm = new Vue({
  553. el: el,
  554. data: {
  555. test: 'b'
  556. },
  557. template: '<input v-model="test" lazy>'
  558. })
  559. expect(el.firstChild.value).toBe('b')
  560. vm.test = 'a'
  561. _.nextTick(function () {
  562. expect(el.firstChild.value).toBe('a')
  563. el.firstChild.value = 'c'
  564. jQuery(el.firstChild).trigger('change')
  565. expect(vm.test).toBe('c')
  566. vm._directives[0].unbind()
  567. el.firstChild.value = 'd'
  568. jQuery(el.firstChild).trigger('change')
  569. expect(vm.test).toBe('c')
  570. // unset jQuery
  571. jQuery = null
  572. done()
  573. })
  574. })
  575. it('support debounce', function (done) {
  576. var spy = jasmine.createSpy()
  577. var vm = new Vue({
  578. el: el,
  579. data: {
  580. test: 'a'
  581. },
  582. watch: {
  583. test: spy
  584. },
  585. template: '<input v-model="test" debounce="100">'
  586. })
  587. el.firstChild.value = 'b'
  588. trigger(el.firstChild, 'input')
  589. setTimeout(function () {
  590. el.firstChild.value = 'c'
  591. trigger(el.firstChild, 'input')
  592. }, 10)
  593. setTimeout(function () {
  594. el.firstChild.value = 'd'
  595. trigger(el.firstChild, 'input')
  596. }, 20)
  597. setTimeout(function () {
  598. expect(spy.calls.count()).toBe(0)
  599. expect(vm.test).toBe('a')
  600. }, 30)
  601. setTimeout(function () {
  602. expect(spy.calls.count()).toBe(1)
  603. expect(vm.test).toBe('d')
  604. done()
  605. }, 200)
  606. })
  607. })
  608. }