model-select.spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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('multiple with static template', () => {
  252. const vm = new Vue({
  253. template:
  254. '<select multiple>' +
  255. '<option selected>a</option>' +
  256. '<option selected>b</option>' +
  257. '<option selected>c</option>' +
  258. '</select>'
  259. }).$mount()
  260. var opts = vm.$el.options
  261. expect(opts[0].selected).toBe(true)
  262. expect(opts[1].selected).toBe(true)
  263. expect(opts[2].selected).toBe(true)
  264. })
  265. it('multiple selects', (done) => {
  266. const spy = jasmine.createSpy()
  267. const vm = new Vue({
  268. data: {
  269. selections: ['', ''],
  270. selectBoxes: [
  271. [
  272. { value: 'foo', text: 'foo' },
  273. { value: 'bar', text: 'bar' }
  274. ],
  275. [
  276. { value: 'day', text: 'day' },
  277. { value: 'night', text: 'night' }
  278. ]
  279. ]
  280. },
  281. watch: {
  282. selections: spy
  283. },
  284. template:
  285. '<div>' +
  286. '<select v-for="(item, index) in selectBoxes" v-model="selections[index]">' +
  287. '<option v-for="element in item" v-bind:value="element.value" v-text="element.text"></option>' +
  288. '</select>' +
  289. '<span ref="rs">{{selections}}</span>' +
  290. '</div>'
  291. }).$mount()
  292. document.body.appendChild(vm.$el)
  293. var selects = vm.$el.getElementsByTagName('select')
  294. var select0 = selects[0]
  295. select0.options[0].selected = true
  296. triggerEvent(select0, 'change')
  297. waitForUpdate(() => {
  298. expect(spy).toHaveBeenCalled()
  299. expect(vm.selections).toEqual(['foo', ''])
  300. }).then(done)
  301. })
  302. it('.number modifier', () => {
  303. const vm = new Vue({
  304. data: {
  305. test: 2
  306. },
  307. template:
  308. '<select v-model.number="test">' +
  309. '<option value="1">a</option>' +
  310. '<option :value="2">b</option>' +
  311. '<option :value="3">c</option>' +
  312. '</select>'
  313. }).$mount()
  314. document.body.appendChild(vm.$el)
  315. updateSelect(vm.$el, '1')
  316. triggerEvent(vm.$el, 'change')
  317. expect(vm.test).toBe(1)
  318. })
  319. it('should respect different pritive type value', (done) => {
  320. const vm = new Vue({
  321. data: {
  322. test: 0
  323. },
  324. template:
  325. '<select v-model.number="test">' +
  326. '<option value="">a</option>' +
  327. '<option value="0">b</option>' +
  328. '<option value="1">c</option>' +
  329. '<option value="false">c</option>' +
  330. '<option value="true">c</option>' +
  331. '</select>'
  332. }).$mount()
  333. var opts = vm.$el.options
  334. expect(opts[0].selected).toBe(false)
  335. expect(opts[1].selected).toBe(true)
  336. expect(opts[2].selected).toBe(false)
  337. expect(opts[3].selected).toBe(false)
  338. expect(opts[4].selected).toBe(false)
  339. vm.test = 1
  340. waitForUpdate(() => {
  341. expect(opts[0].selected).toBe(false)
  342. expect(opts[1].selected).toBe(false)
  343. expect(opts[2].selected).toBe(true)
  344. expect(opts[3].selected).toBe(false)
  345. expect(opts[4].selected).toBe(false)
  346. vm.test = ''
  347. }).then(() => {
  348. expect(opts[0].selected).toBe(true)
  349. expect(opts[1].selected).toBe(false)
  350. expect(opts[2].selected).toBe(false)
  351. expect(opts[3].selected).toBe(false)
  352. expect(opts[4].selected).toBe(false)
  353. vm.test = false
  354. }).then(() => {
  355. expect(opts[0].selected).toBe(false)
  356. expect(opts[1].selected).toBe(false)
  357. expect(opts[2].selected).toBe(false)
  358. expect(opts[3].selected).toBe(true)
  359. expect(opts[4].selected).toBe(false)
  360. vm.test = true
  361. }).then(() => {
  362. expect(opts[0].selected).toBe(false)
  363. expect(opts[1].selected).toBe(false)
  364. expect(opts[2].selected).toBe(false)
  365. expect(opts[3].selected).toBe(false)
  366. expect(opts[4].selected).toBe(true)
  367. }).then(done)
  368. })
  369. it('should warn inline selected', () => {
  370. const vm = new Vue({
  371. data: {
  372. test: null
  373. },
  374. template:
  375. '<select v-model="test">' +
  376. '<option selected>a</option>' +
  377. '</select>'
  378. }).$mount()
  379. expect(vm.$el.selectedIndex).toBe(-1)
  380. expect('inline selected attributes on <option> will be ignored when using v-model')
  381. .toHaveBeenWarned()
  382. })
  383. it('should warn multiple with non-Array value', done => {
  384. new Vue({
  385. data: {
  386. test: 'meh'
  387. },
  388. template:
  389. '<select v-model="test" multiple></select>'
  390. }).$mount()
  391. // IE warns on a setTimeout as well
  392. setTimeout(() => {
  393. expect('<select multiple v-model="test"> expects an Array value for its binding, but got String')
  394. .toHaveBeenWarned()
  395. done()
  396. }, 0)
  397. })
  398. })