2
0

model-select.spec.js 14 KB

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