model_spec.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. it('radio buttons', function (done) {
  37. var vm = new Vue({
  38. el: el,
  39. data: {
  40. test: 'a'
  41. },
  42. template:
  43. '<input type="radio" value="a" v-model="test" name="test">' +
  44. '<input type="radio" value="b" v-model="test" name="test">'
  45. })
  46. expect(el.childNodes[0].checked).toBe(true)
  47. expect(el.childNodes[1].checked).toBe(false)
  48. vm.test = 'b'
  49. _.nextTick(function () {
  50. expect(el.childNodes[0].checked).toBe(false)
  51. expect(el.childNodes[1].checked).toBe(true)
  52. el.childNodes[0].click()
  53. expect(el.childNodes[0].checked).toBe(true)
  54. expect(el.childNodes[1].checked).toBe(false)
  55. expect(vm.test).toBe('a')
  56. vm._directives[1].unbind()
  57. el.childNodes[1].click()
  58. expect(vm.test).toBe('a')
  59. done()
  60. })
  61. })
  62. it('radio default value', function () {
  63. var vm = new Vue({
  64. el: el,
  65. data: {},
  66. template: '<input type="radio" checked value="a" v-model="test">'
  67. })
  68. expect(vm.test).toBe('a')
  69. })
  70. it('checkbox', function (done) {
  71. var vm = new Vue({
  72. el: el,
  73. data: {
  74. test: true
  75. },
  76. template: '<input type="checkbox" v-model="test">'
  77. })
  78. expect(el.firstChild.checked).toBe(true)
  79. vm.test = false
  80. _.nextTick(function () {
  81. expect(el.firstChild.checked).toBe(false)
  82. expect(vm.test).toBe(false)
  83. el.firstChild.click()
  84. expect(el.firstChild.checked).toBe(true)
  85. expect(vm.test).toBe(true)
  86. vm._directives[0].unbind()
  87. el.firstChild.click()
  88. expect(el.firstChild.checked).toBe(false)
  89. expect(vm.test).toBe(true)
  90. done()
  91. })
  92. })
  93. it('checkbox default value', function () {
  94. var vm = new Vue({
  95. el: el,
  96. data: {},
  97. template: '<input type="checkbox" checked v-model="test">'
  98. })
  99. expect(vm.test).toBe(true)
  100. })
  101. it('select', function (done) {
  102. var vm = new Vue({
  103. el: el,
  104. data: {
  105. test: 'b'
  106. },
  107. template:
  108. '<select v-model="test">' +
  109. '<option>a</option>' +
  110. '<option>b</option>' +
  111. '<option>c</option>' +
  112. '</select>'
  113. })
  114. expect(vm.test).toBe('b')
  115. expect(el.firstChild.value).toBe('b')
  116. expect(el.firstChild.childNodes[1].selected).toBe(true)
  117. vm.test = 'c'
  118. _.nextTick(function () {
  119. expect(el.firstChild.value).toBe('c')
  120. expect(el.firstChild.childNodes[2].selected).toBe(true)
  121. updateSelect(el.firstChild, 'a')
  122. trigger(el.firstChild, 'change')
  123. expect(vm.test).toBe('a')
  124. done()
  125. })
  126. })
  127. it('select default value', function () {
  128. var vm = new Vue({
  129. el: el,
  130. data: {
  131. test: 'a'
  132. },
  133. template:
  134. '<select v-model="test">' +
  135. '<option>a</option>' +
  136. '<option selected>b</option>' +
  137. '</select>'
  138. })
  139. expect(vm.test).toBe('b')
  140. expect(el.firstChild.value).toBe('b')
  141. expect(el.firstChild.childNodes[1].selected).toBe(true)
  142. })
  143. it('select + multiple', function (done) {
  144. var vm = new Vue({
  145. el: el,
  146. data: {
  147. test: [2] // test number soft equal
  148. },
  149. template:
  150. '<select v-model="test" multiple>' +
  151. '<option>1</option>' +
  152. '<option>2</option>' +
  153. '<option>3</option>' +
  154. '</select>'
  155. })
  156. var opts = el.firstChild.options
  157. expect(opts[0].selected).toBe(false)
  158. expect(opts[1].selected).toBe(true)
  159. expect(opts[2].selected).toBe(false)
  160. vm.test = [1, '3'] // mix of number/string
  161. _.nextTick(function () {
  162. expect(opts[0].selected).toBe(true)
  163. expect(opts[1].selected).toBe(false)
  164. expect(opts[2].selected).toBe(true)
  165. opts[0].selected = false
  166. opts[1].selected = true
  167. trigger(el.firstChild, 'change')
  168. expect(vm.test[0]).toBe('2')
  169. expect(vm.test[1]).toBe('3')
  170. done()
  171. })
  172. })
  173. it('select + multiple default value', function () {
  174. var vm = new Vue({
  175. el: el,
  176. data: {},
  177. template:
  178. '<select v-model="test" multiple>' +
  179. '<option>a</option>' +
  180. '<option selected>b</option>' +
  181. '<option selected>c</option>' +
  182. '</select>'
  183. })
  184. expect(vm.test[0]).toBe('b')
  185. expect(vm.test[1]).toBe('c')
  186. })
  187. it('select + options', function (done) {
  188. var vm = new Vue({
  189. el: el,
  190. data: {
  191. test: 'b',
  192. opts: ['a', 'b', 'c']
  193. },
  194. template: '<select v-model="test" options="opts"></select>'
  195. })
  196. var opts = el.firstChild.options
  197. expect(opts.length).toBe(3)
  198. expect(opts[0].selected).toBe(false)
  199. expect(opts[1].selected).toBe(true)
  200. expect(opts[2].selected).toBe(false)
  201. vm.opts = ['b', 'c']
  202. _.nextTick(function () {
  203. expect(opts.length).toBe(2)
  204. expect(opts[0].selected).toBe(true)
  205. expect(opts[1].selected).toBe(false)
  206. // should teardown option watcher when unbind
  207. expect(vm._watcherList.length).toBe(2)
  208. vm._directives[0].unbind()
  209. expect(vm._watcherList.length).toBe(0)
  210. done()
  211. })
  212. })
  213. it('select + options + text', function () {
  214. var vm = new Vue({
  215. el: el,
  216. data: {
  217. test: 'b',
  218. opts: [
  219. { text: 'A', value: 'a' },
  220. { text: 'B', value: 'b' }
  221. ]
  222. },
  223. template: '<select v-model="test" options="opts"></select>'
  224. })
  225. expect(el.firstChild.innerHTML).toBe(
  226. '<option value="a">A</option>' +
  227. '<option value="b">B</option>'
  228. )
  229. var opts = el.firstChild.options
  230. expect(opts[0].selected).toBe(false)
  231. expect(opts[1].selected).toBe(true)
  232. })
  233. it('select + options + optgroup', function () {
  234. var vm = new Vue({
  235. el: el,
  236. data: {
  237. test: 'b',
  238. opts: [
  239. { label: 'A', options: ['a','b'] },
  240. { label: 'B', options: ['c'] }
  241. ]
  242. },
  243. template: '<select v-model="test" options="opts"></select>'
  244. })
  245. expect(el.firstChild.innerHTML).toBe(
  246. '<optgroup label="A">' +
  247. '<option value="a">a</option><option value="b">b</option>' +
  248. '</optgroup>' +
  249. '<optgroup label="B">' +
  250. '<option value="c">c</option>' +
  251. '</optgroup>'
  252. )
  253. var opts = el.firstChild.options
  254. expect(opts[0].selected).toBe(false)
  255. expect(opts[1].selected).toBe(true)
  256. expect(opts[2].selected).toBe(false)
  257. })
  258. it('select + number', function () {
  259. var vm = new Vue({
  260. el: el,
  261. data: {
  262. test: '1'
  263. },
  264. template: '<select v-model="test" number><option value="1">1</option></select>'
  265. })
  266. expect(vm.test).toBe('1')
  267. trigger(vm.$el.firstChild, 'change')
  268. expect(vm.test).toBe(1)
  269. })
  270. it('select + number + multiple', function () {
  271. var vm = new Vue({
  272. el: el,
  273. data: {
  274. test: []
  275. },
  276. template: '<select v-model="test" multiple number><option>1</option><option>2</option></select>'
  277. })
  278. ;[].forEach.call(el.querySelectorAll('option'), function (o) {
  279. o.selected = true
  280. })
  281. trigger(el.firstChild, 'change')
  282. expect(vm.test[0]).toBe(1)
  283. expect(vm.test[1]).toBe(2)
  284. })
  285. it('select + number initial value', function () {
  286. var vm = new Vue({
  287. el: el,
  288. data: {
  289. test: '1'
  290. },
  291. template: '<select v-model="test" number><option value="1" selected>1</option></select>'
  292. })
  293. expect(vm.test).toBe(1)
  294. })
  295. it('text', function (done) {
  296. var vm = new Vue({
  297. el: el,
  298. data: {
  299. test: 'b'
  300. },
  301. template: '<input v-model="test">'
  302. })
  303. expect(el.firstChild.value).toBe('b')
  304. vm.test = 'a'
  305. _.nextTick(function () {
  306. expect(el.firstChild.value).toBe('a')
  307. el.firstChild.value = 'c'
  308. trigger(el.firstChild, 'input')
  309. expect(vm.test).toBe('c')
  310. vm._directives[0].unbind()
  311. el.firstChild.value = 'd'
  312. trigger(el.firstChild, 'input')
  313. expect(vm.test).toBe('c')
  314. done()
  315. })
  316. })
  317. it('text default value', function () {
  318. var vm = new Vue({
  319. el: el,
  320. data: {
  321. test: 'b'
  322. },
  323. template: '<input v-model="test | test" value="a">',
  324. filters: {
  325. test: {
  326. read: function (v) {
  327. return v.slice(0, -1)
  328. },
  329. write: function (v) {
  330. return v + 'c'
  331. }
  332. }
  333. }
  334. })
  335. expect(vm.test).toBe('ac')
  336. expect(el.firstChild.value).toBe('a')
  337. })
  338. it('text lazy', function () {
  339. var vm = new Vue({
  340. el: el,
  341. data: {
  342. test: 'b'
  343. },
  344. template: '<input v-model="test" lazy>'
  345. })
  346. expect(el.firstChild.value).toBe('b')
  347. expect(vm.test).toBe('b')
  348. el.firstChild.value = 'c'
  349. trigger(el.firstChild, 'input')
  350. expect(vm.test).toBe('b')
  351. trigger(el.firstChild, 'change')
  352. expect(vm.test).toBe('c')
  353. })
  354. it('text with filters', function (done) {
  355. var vm = new Vue({
  356. el: el,
  357. data: {
  358. test: 'b'
  359. },
  360. filters: {
  361. test: {
  362. write: function (val) {
  363. return val.toUpperCase()
  364. }
  365. }
  366. },
  367. template: '<input v-model="test | uppercase | test">'
  368. })
  369. expect(el.firstChild.value).toBe('B')
  370. el.firstChild.value = 'cc'
  371. trigger(el.firstChild, 'input')
  372. _.nextTick(function () {
  373. expect(el.firstChild.value).toBe('CC')
  374. expect(vm.test).toBe('CC')
  375. done()
  376. })
  377. })
  378. it('number', function () {
  379. var vm = new Vue({
  380. el: el,
  381. data: {
  382. test: 1
  383. },
  384. template: '<input v-model="test" value="2" number>'
  385. })
  386. expect(vm.test).toBe(2)
  387. el.firstChild.value = 3
  388. trigger(el.firstChild, 'input')
  389. expect(vm.test).toBe(3)
  390. })
  391. it('IE9 cut and delete', function (done) {
  392. var ie9 = _.isIE9
  393. _.isIE9 = true
  394. var vm = new Vue({
  395. el: el,
  396. data: {
  397. test: 'aaa'
  398. },
  399. template: '<input v-model="test">'
  400. })
  401. var input = el.firstChild
  402. input.value = 'aa'
  403. trigger(input, 'cut')
  404. _.nextTick(function () {
  405. expect(vm.test).toBe('aa')
  406. input.value = 'a'
  407. trigger(input, 'keyup', function (e) {
  408. e.keyCode = 8
  409. })
  410. expect(vm.test).toBe('a')
  411. // teardown
  412. vm._directives[0].unbind()
  413. input.value = 'bbb'
  414. trigger(input, 'keyup', function (e) {
  415. e.keyCode = 8
  416. })
  417. expect(vm.test).toBe('a')
  418. _.isIE9 = ie9
  419. done()
  420. })
  421. })
  422. it('text + compositionevents', function (done) {
  423. var vm = new Vue({
  424. el: el,
  425. data: {
  426. test: 'aaa',
  427. test2: 'bbb'
  428. },
  429. template: '<input v-model="test"><input v-model="test2 | uppsercase">'
  430. })
  431. var input = el.firstChild
  432. var input2 = el.childNodes[1]
  433. trigger(input, 'compositionstart')
  434. trigger(input2, 'compositionstart')
  435. input.value = input2.value = 'ccc'
  436. // input before composition unlock should not call set
  437. trigger(input, 'input')
  438. trigger(input2, 'input')
  439. expect(vm.test).toBe('aaa')
  440. expect(vm.test2).toBe('bbb')
  441. // after composition unlock it should work
  442. trigger(input, 'compositionend')
  443. trigger(input2, 'compositionend')
  444. trigger(input, 'input')
  445. trigger(input2, 'input')
  446. expect(vm.test).toBe('ccc')
  447. expect(vm.test2).toBe('ccc')
  448. // IE complains about "unspecified error" when calling
  449. // setSelectionRange() on an input element that's been
  450. // removed from the DOM, so we wait until the
  451. // selection range callback has fired to end this test.
  452. _.nextTick(done)
  453. })
  454. it('textarea', function () {
  455. var vm = new Vue({
  456. el: el,
  457. data: {
  458. test: 'b',
  459. b: 'BB'
  460. },
  461. template: '<textarea v-model="test">a {{b}} c</textarea>'
  462. })
  463. expect(vm.test).toBe('a BB c')
  464. expect(el.firstChild.value).toBe('a BB c')
  465. })
  466. it('warn invalid tag', function () {
  467. var vm = new Vue({
  468. el: el,
  469. template: '<div v-model="test"></div>'
  470. })
  471. expect(_.warn).toHaveBeenCalled()
  472. })
  473. it('warn invalid option value', function () {
  474. var vm = new Vue({
  475. el: el,
  476. data: { a: 123 },
  477. template: '<select v-model="test" options="a"></select>'
  478. })
  479. expect(_.warn).toHaveBeenCalled()
  480. })
  481. it('warn read-only filters', function () {
  482. var vm = new Vue({
  483. el: el,
  484. template: '<input v-model="abc | test">',
  485. filters: {
  486. test: function (v) {
  487. return v
  488. }
  489. }
  490. })
  491. expect(_.warn).toHaveBeenCalled()
  492. })
  493. })
  494. }