model-select.spec.js 12 KB

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