model-select.spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. import Vue from 'vue'
  2. import { looseEqual } from 'shared/util'
  3. // Android 4.4 Chrome 30 has the bug that a multi-select option cannot be
  4. // deseleted by setting its "selected" prop via JavaScript.
  5. function hasMultiSelectBug () {
  6. var s = document.createElement('select')
  7. s.setAttribute('multiple', '')
  8. var o = document.createElement('option')
  9. s.appendChild(o)
  10. o.selected = true
  11. o.selected = false
  12. return o.selected !== false
  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. var options = el.options
  20. var i = options.length
  21. while (i--) {
  22. if (looseEqual(getValue(options[i]), value)) {
  23. options[i].selected = true
  24. break
  25. }
  26. }
  27. }
  28. function getValue (option) {
  29. return '_value' in option
  30. ? option._value
  31. : option.value || option.text
  32. }
  33. describe('Directive v-model select', () => {
  34. it('should work', done => {
  35. const vm = new Vue({
  36. data: {
  37. test: 'b'
  38. },
  39. template:
  40. '<select v-model="test">' +
  41. '<option>a</option>' +
  42. '<option>b</option>' +
  43. '<option>c</option>' +
  44. '</select>'
  45. }).$mount()
  46. document.body.appendChild(vm.$el)
  47. expect(vm.test).toBe('b')
  48. expect(vm.$el.value).toBe('b')
  49. expect(vm.$el.childNodes[1].selected).toBe(true)
  50. vm.test = 'c'
  51. waitForUpdate(function () {
  52. expect(vm.$el.value).toBe('c')
  53. expect(vm.$el.childNodes[2].selected).toBe(true)
  54. updateSelect(vm.$el, 'a')
  55. triggerEvent(vm.$el, 'change')
  56. expect(vm.test).toBe('a')
  57. }).then(done)
  58. })
  59. it('should work with value bindings', done => {
  60. const vm = new Vue({
  61. data: {
  62. test: 2
  63. },
  64. template:
  65. '<select v-model="test">' +
  66. '<option value="1">a</option>' +
  67. '<option :value="2">b</option>' +
  68. '<option :value="3">c</option>' +
  69. '</select>'
  70. }).$mount()
  71. document.body.appendChild(vm.$el)
  72. expect(vm.$el.value).toBe('2')
  73. expect(vm.$el.childNodes[1].selected).toBe(true)
  74. vm.test = 3
  75. waitForUpdate(function () {
  76. expect(vm.$el.value).toBe('3')
  77. expect(vm.$el.childNodes[2].selected).toBe(true)
  78. updateSelect(vm.$el, '1')
  79. triggerEvent(vm.$el, 'change')
  80. expect(vm.test).toBe('1')
  81. }).then(done)
  82. })
  83. it('should work with value bindings (object loose equal)', done => {
  84. const vm = new Vue({
  85. data: {
  86. test: { a: 2 }
  87. },
  88. template:
  89. '<select v-model="test">' +
  90. '<option value="1">a</option>' +
  91. '<option :value="{ a: 2 }">b</option>' +
  92. '<option :value="{ a: 3 }">c</option>' +
  93. '</select>'
  94. }).$mount()
  95. document.body.appendChild(vm.$el)
  96. expect(vm.$el.childNodes[1].selected).toBe(true)
  97. vm.test = { a: 3 }
  98. waitForUpdate(function () {
  99. expect(vm.$el.childNodes[2].selected).toBe(true)
  100. updateSelect(vm.$el, '1')
  101. triggerEvent(vm.$el, 'change')
  102. expect(vm.test).toBe('1')
  103. updateSelect(vm.$el, { a: 2 })
  104. triggerEvent(vm.$el, 'change')
  105. expect(vm.test).toEqual({ a: 2 })
  106. }).then(done)
  107. })
  108. it('should work with v-for', done => {
  109. const vm = new Vue({
  110. data: {
  111. test: 'b',
  112. opts: ['a', 'b', 'c']
  113. },
  114. template:
  115. '<select v-model="test">' +
  116. '<option v-for="o in opts">{{ o }}</option>' +
  117. '</select>'
  118. }).$mount()
  119. document.body.appendChild(vm.$el)
  120. expect(vm.test).toBe('b')
  121. expect(vm.$el.value).toBe('b')
  122. expect(vm.$el.childNodes[1].selected).toBe(true)
  123. vm.test = 'c'
  124. waitForUpdate(function () {
  125. expect(vm.$el.value).toBe('c')
  126. expect(vm.$el.childNodes[2].selected).toBe(true)
  127. updateSelect(vm.$el, 'a')
  128. triggerEvent(vm.$el, 'change')
  129. expect(vm.test).toBe('a')
  130. // update v-for opts
  131. vm.opts = ['d', 'a']
  132. }).then(() => {
  133. expect(vm.$el.childNodes[0].selected).toBe(false)
  134. expect(vm.$el.childNodes[1].selected).toBe(true)
  135. }).then(done)
  136. })
  137. it('should work with v-for & value bindings', done => {
  138. const vm = new Vue({
  139. data: {
  140. test: 2,
  141. opts: [1, 2, 3]
  142. },
  143. template:
  144. '<select v-model="test">' +
  145. '<option v-for="o in opts" :value="o">optio {{ o }}</option>' +
  146. '</select>'
  147. }).$mount()
  148. document.body.appendChild(vm.$el)
  149. expect(vm.$el.value).toBe('2')
  150. expect(vm.$el.childNodes[1].selected).toBe(true)
  151. vm.test = 3
  152. waitForUpdate(function () {
  153. expect(vm.$el.value).toBe('3')
  154. expect(vm.$el.childNodes[2].selected).toBe(true)
  155. updateSelect(vm.$el, 1)
  156. triggerEvent(vm.$el, 'change')
  157. expect(vm.test).toBe(1)
  158. // update v-for opts
  159. vm.opts = [0, 1]
  160. }).then(() => {
  161. expect(vm.$el.childNodes[0].selected).toBe(false)
  162. expect(vm.$el.childNodes[1].selected).toBe(true)
  163. }).then(done)
  164. })
  165. it('should work with select which has no default selected options', (done) => {
  166. const spy = jasmine.createSpy()
  167. const vm = new Vue({
  168. data: {
  169. id: 4,
  170. list: [1, 2, 3],
  171. testChange: 5
  172. },
  173. template:
  174. '<div>' +
  175. '<select @change="test" v-model="id">' +
  176. '<option v-for="item in list" :value="item">{{item}}</option>' +
  177. '</select>' +
  178. '{{testChange}}' +
  179. '</div>',
  180. methods: {
  181. test: spy
  182. }
  183. }).$mount()
  184. document.body.appendChild(vm.$el)
  185. vm.testChange = 10
  186. waitForUpdate(() => {
  187. expect(spy.calls.count()).toBe(0)
  188. }).then(done)
  189. })
  190. if (!hasMultiSelectBug()) {
  191. it('multiple', done => {
  192. const vm = new Vue({
  193. data: {
  194. test: ['b']
  195. },
  196. template:
  197. '<select v-model="test" multiple>' +
  198. '<option>a</option>' +
  199. '<option>b</option>' +
  200. '<option>c</option>' +
  201. '</select>'
  202. }).$mount()
  203. var opts = vm.$el.options
  204. expect(opts[0].selected).toBe(false)
  205. expect(opts[1].selected).toBe(true)
  206. expect(opts[2].selected).toBe(false)
  207. vm.test = ['a', 'c']
  208. waitForUpdate(() => {
  209. expect(opts[0].selected).toBe(true)
  210. expect(opts[1].selected).toBe(false)
  211. expect(opts[2].selected).toBe(true)
  212. opts[0].selected = false
  213. opts[1].selected = true
  214. triggerEvent(vm.$el, 'change')
  215. expect(vm.test).toEqual(['b', 'c'])
  216. }).then(done)
  217. })
  218. it('multiple + v-for', done => {
  219. const vm = new Vue({
  220. data: {
  221. test: ['b'],
  222. opts: ['a', 'b', 'c']
  223. },
  224. template:
  225. '<select v-model="test" multiple>' +
  226. '<option v-for="o in opts">{{ o }}</option>' +
  227. '</select>'
  228. }).$mount()
  229. var opts = vm.$el.options
  230. expect(opts[0].selected).toBe(false)
  231. expect(opts[1].selected).toBe(true)
  232. expect(opts[2].selected).toBe(false)
  233. vm.test = ['a', 'c']
  234. waitForUpdate(() => {
  235. expect(opts[0].selected).toBe(true)
  236. expect(opts[1].selected).toBe(false)
  237. expect(opts[2].selected).toBe(true)
  238. opts[0].selected = false
  239. opts[1].selected = true
  240. triggerEvent(vm.$el, 'change')
  241. expect(vm.test).toEqual(['b', 'c'])
  242. // update v-for opts
  243. vm.opts = ['c', 'd']
  244. }).then(() => {
  245. expect(opts[0].selected).toBe(true)
  246. expect(opts[1].selected).toBe(false)
  247. expect(vm.test).toEqual(['c']) // should remove 'd' which no longer has a matching option
  248. }).then(done)
  249. })
  250. }
  251. it('should work with multiple binding', (done) => {
  252. const spy = jasmine.createSpy()
  253. const vm = new Vue({
  254. data: {
  255. isMultiple: true,
  256. selections: ['1']
  257. },
  258. template:
  259. '<select v-model="selections" :multiple="isMultiple">' +
  260. '<option value="1">item 1</option>' +
  261. '<option value="2">item 2</option>' +
  262. '</select>',
  263. watch: {
  264. selections: spy
  265. }
  266. }).$mount()
  267. document.body.appendChild(vm.$el)
  268. vm.$el.options[1].selected = true
  269. triggerEvent(vm.$el, 'change')
  270. waitForUpdate(() => {
  271. expect(spy).toHaveBeenCalled()
  272. expect(vm.selections).toEqual(['1', '2'])
  273. }).then(done)
  274. })
  275. it('should not have multiple attr with falsy values except \'\'', () => {
  276. const vm = new Vue({
  277. template:
  278. '<div>' +
  279. '<select id="undefined" :multiple="undefined"></select>' +
  280. '<select id="null" :multiple="null"></select>' +
  281. '<select id="false" :multiple="false"></select>' +
  282. '<select id="string" :multiple="\'\'"></select>' +
  283. '</div>'
  284. }).$mount()
  285. expect(vm.$el.querySelector('#undefined').multiple).toEqual(false)
  286. expect(vm.$el.querySelector('#null').multiple).toEqual(false)
  287. expect(vm.$el.querySelector('#false').multiple).toEqual(false)
  288. expect(vm.$el.querySelector('#string').multiple).toEqual(true)
  289. })
  290. it('multiple with static template', () => {
  291. const vm = new Vue({
  292. template:
  293. '<select multiple>' +
  294. '<option selected>a</option>' +
  295. '<option selected>b</option>' +
  296. '<option selected>c</option>' +
  297. '</select>'
  298. }).$mount()
  299. var opts = vm.$el.options
  300. expect(opts[0].selected).toBe(true)
  301. expect(opts[1].selected).toBe(true)
  302. expect(opts[2].selected).toBe(true)
  303. })
  304. it('multiple selects', (done) => {
  305. const spy = jasmine.createSpy()
  306. const vm = new Vue({
  307. data: {
  308. selections: ['', ''],
  309. selectBoxes: [
  310. [
  311. { value: 'foo', text: 'foo' },
  312. { value: 'bar', text: 'bar' }
  313. ],
  314. [
  315. { value: 'day', text: 'day' },
  316. { value: 'night', text: 'night' }
  317. ]
  318. ]
  319. },
  320. watch: {
  321. selections: spy
  322. },
  323. template:
  324. '<div>' +
  325. '<select v-for="(item, index) in selectBoxes" v-model="selections[index]">' +
  326. '<option v-for="element in item" v-bind:value="element.value" v-text="element.text"></option>' +
  327. '</select>' +
  328. '<span ref="rs">{{selections}}</span>' +
  329. '</div>'
  330. }).$mount()
  331. document.body.appendChild(vm.$el)
  332. var selects = vm.$el.getElementsByTagName('select')
  333. var select0 = selects[0]
  334. select0.options[0].selected = true
  335. triggerEvent(select0, 'change')
  336. waitForUpdate(() => {
  337. expect(spy).toHaveBeenCalled()
  338. expect(vm.selections).toEqual(['foo', ''])
  339. }).then(done)
  340. })
  341. it('.number modifier', () => {
  342. const vm = new Vue({
  343. data: {
  344. test: 2
  345. },
  346. template:
  347. '<select v-model.number="test">' +
  348. '<option value="1">a</option>' +
  349. '<option :value="2">b</option>' +
  350. '<option :value="3">c</option>' +
  351. '</select>'
  352. }).$mount()
  353. document.body.appendChild(vm.$el)
  354. updateSelect(vm.$el, '1')
  355. triggerEvent(vm.$el, 'change')
  356. expect(vm.test).toBe(1)
  357. })
  358. it('should respect different pritive type value', (done) => {
  359. const vm = new Vue({
  360. data: {
  361. test: 0
  362. },
  363. template:
  364. '<select v-model.number="test">' +
  365. '<option value="">a</option>' +
  366. '<option value="0">b</option>' +
  367. '<option value="1">c</option>' +
  368. '<option value="false">c</option>' +
  369. '<option value="true">c</option>' +
  370. '</select>'
  371. }).$mount()
  372. var opts = vm.$el.options
  373. expect(opts[0].selected).toBe(false)
  374. expect(opts[1].selected).toBe(true)
  375. expect(opts[2].selected).toBe(false)
  376. expect(opts[3].selected).toBe(false)
  377. expect(opts[4].selected).toBe(false)
  378. vm.test = 1
  379. waitForUpdate(() => {
  380. expect(opts[0].selected).toBe(false)
  381. expect(opts[1].selected).toBe(false)
  382. expect(opts[2].selected).toBe(true)
  383. expect(opts[3].selected).toBe(false)
  384. expect(opts[4].selected).toBe(false)
  385. vm.test = ''
  386. }).then(() => {
  387. expect(opts[0].selected).toBe(true)
  388. expect(opts[1].selected).toBe(false)
  389. expect(opts[2].selected).toBe(false)
  390. expect(opts[3].selected).toBe(false)
  391. expect(opts[4].selected).toBe(false)
  392. vm.test = false
  393. }).then(() => {
  394. expect(opts[0].selected).toBe(false)
  395. expect(opts[1].selected).toBe(false)
  396. expect(opts[2].selected).toBe(false)
  397. expect(opts[3].selected).toBe(true)
  398. expect(opts[4].selected).toBe(false)
  399. vm.test = true
  400. }).then(() => {
  401. expect(opts[0].selected).toBe(false)
  402. expect(opts[1].selected).toBe(false)
  403. expect(opts[2].selected).toBe(false)
  404. expect(opts[3].selected).toBe(false)
  405. expect(opts[4].selected).toBe(true)
  406. }).then(done)
  407. })
  408. it('should warn inline selected', () => {
  409. const vm = new Vue({
  410. data: {
  411. test: null
  412. },
  413. template:
  414. '<select v-model="test">' +
  415. '<option selected>a</option>' +
  416. '</select>'
  417. }).$mount()
  418. expect(vm.$el.selectedIndex).toBe(-1)
  419. expect('inline selected attributes on <option> will be ignored when using v-model')
  420. .toHaveBeenWarned()
  421. })
  422. it('should warn multiple with non-Array value', done => {
  423. new Vue({
  424. data: {
  425. test: 'meh'
  426. },
  427. template:
  428. '<select v-model="test" multiple></select>'
  429. }).$mount()
  430. // IE warns on a setTimeout as well
  431. setTimeout(() => {
  432. expect('<select multiple v-model="test"> expects an Array value for its binding, but got String')
  433. .toHaveBeenWarned()
  434. done()
  435. }, 0)
  436. })
  437. })