vModel.spec.ts 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. import {
  2. h,
  3. render,
  4. nextTick,
  5. defineComponent,
  6. vModelDynamic,
  7. withDirectives,
  8. VNode,
  9. ref
  10. } from '@vue/runtime-dom'
  11. const triggerEvent = (type: string, el: Element) => {
  12. const event = new Event(type)
  13. el.dispatchEvent(event)
  14. }
  15. const withVModel = (node: VNode, arg: any, mods?: any) =>
  16. withDirectives(node, [[vModelDynamic, arg, '', mods]])
  17. const setValue = function (this: any, value: any) {
  18. this.value = value
  19. }
  20. let root: any
  21. beforeEach(() => {
  22. root = document.createElement('div') as any
  23. })
  24. describe('vModel', () => {
  25. it('should work with text input', async () => {
  26. const manualListener = jest.fn()
  27. const component = defineComponent({
  28. data() {
  29. return { value: null }
  30. },
  31. render() {
  32. return [
  33. withVModel(
  34. h('input', {
  35. 'onUpdate:modelValue': setValue.bind(this),
  36. onInput: () => {
  37. manualListener(data.value)
  38. }
  39. }),
  40. this.value
  41. )
  42. ]
  43. }
  44. })
  45. render(h(component), root)
  46. const input = root.querySelector('input')!
  47. const data = root._vnode.component.data
  48. expect(input.value).toEqual('')
  49. input.value = 'foo'
  50. triggerEvent('input', input)
  51. await nextTick()
  52. expect(data.value).toEqual('foo')
  53. // #1931
  54. expect(manualListener).toHaveBeenCalledWith('foo')
  55. data.value = 'bar'
  56. await nextTick()
  57. expect(input.value).toEqual('bar')
  58. data.value = undefined
  59. await nextTick()
  60. expect(input.value).toEqual('')
  61. })
  62. it('should work with number input', async () => {
  63. const component = defineComponent({
  64. data() {
  65. return { value: null }
  66. },
  67. render() {
  68. return [
  69. withVModel(
  70. h('input', {
  71. type: 'number',
  72. 'onUpdate:modelValue': setValue.bind(this)
  73. }),
  74. this.value
  75. )
  76. ]
  77. }
  78. })
  79. render(h(component), root)
  80. const input = root.querySelector('input')!
  81. const data = root._vnode.component.data
  82. expect(input.value).toEqual('')
  83. expect(input.type).toEqual('number')
  84. input.value = 1
  85. triggerEvent('input', input)
  86. await nextTick()
  87. expect(typeof data.value).toEqual('number')
  88. expect(data.value).toEqual(1)
  89. })
  90. it('should work with multiple listeners', async () => {
  91. const spy = jest.fn()
  92. const component = defineComponent({
  93. data() {
  94. return { value: null }
  95. },
  96. render() {
  97. return [
  98. withVModel(
  99. h('input', {
  100. 'onUpdate:modelValue': [setValue.bind(this), spy]
  101. }),
  102. this.value
  103. )
  104. ]
  105. }
  106. })
  107. render(h(component), root)
  108. const input = root.querySelector('input')!
  109. const data = root._vnode.component.data
  110. input.value = 'foo'
  111. triggerEvent('input', input)
  112. await nextTick()
  113. expect(data.value).toEqual('foo')
  114. expect(spy).toHaveBeenCalledWith('foo')
  115. })
  116. it('should work with updated listeners', async () => {
  117. const spy1 = jest.fn()
  118. const spy2 = jest.fn()
  119. const toggle = ref(true)
  120. const component = defineComponent({
  121. render() {
  122. return [
  123. withVModel(
  124. h('input', {
  125. 'onUpdate:modelValue': toggle.value ? spy1 : spy2
  126. }),
  127. 'foo'
  128. )
  129. ]
  130. }
  131. })
  132. render(h(component), root)
  133. const input = root.querySelector('input')!
  134. input.value = 'foo'
  135. triggerEvent('input', input)
  136. await nextTick()
  137. expect(spy1).toHaveBeenCalledWith('foo')
  138. // update listener
  139. toggle.value = false
  140. await nextTick()
  141. input.value = 'bar'
  142. triggerEvent('input', input)
  143. await nextTick()
  144. expect(spy1).not.toHaveBeenCalledWith('bar')
  145. expect(spy2).toHaveBeenCalledWith('bar')
  146. })
  147. it('should work with textarea', async () => {
  148. const component = defineComponent({
  149. data() {
  150. return { value: null }
  151. },
  152. render() {
  153. return [
  154. withVModel(
  155. h('textarea', {
  156. 'onUpdate:modelValue': setValue.bind(this)
  157. }),
  158. this.value
  159. )
  160. ]
  161. }
  162. })
  163. render(h(component), root)
  164. const input = root.querySelector('textarea')
  165. const data = root._vnode.component.data
  166. input.value = 'foo'
  167. triggerEvent('input', input)
  168. await nextTick()
  169. expect(data.value).toEqual('foo')
  170. data.value = 'bar'
  171. await nextTick()
  172. expect(input.value).toEqual('bar')
  173. })
  174. it('should support modifiers', async () => {
  175. const component = defineComponent({
  176. data() {
  177. return { number: null, trim: null, lazy: null }
  178. },
  179. render() {
  180. return [
  181. withVModel(
  182. h('input', {
  183. class: 'number',
  184. 'onUpdate:modelValue': (val: any) => {
  185. this.number = val
  186. }
  187. }),
  188. this.number,
  189. {
  190. number: true
  191. }
  192. ),
  193. withVModel(
  194. h('input', {
  195. class: 'trim',
  196. 'onUpdate:modelValue': (val: any) => {
  197. this.trim = val
  198. }
  199. }),
  200. this.trim,
  201. {
  202. trim: true
  203. }
  204. ),
  205. withVModel(
  206. h('input', {
  207. class: 'lazy',
  208. 'onUpdate:modelValue': (val: any) => {
  209. this.lazy = val
  210. }
  211. }),
  212. this.lazy,
  213. {
  214. lazy: true
  215. }
  216. )
  217. ]
  218. }
  219. })
  220. render(h(component), root)
  221. const number = root.querySelector('.number')
  222. const trim = root.querySelector('.trim')
  223. const lazy = root.querySelector('.lazy')
  224. const data = root._vnode.component.data
  225. number.value = '+01.2'
  226. triggerEvent('input', number)
  227. await nextTick()
  228. expect(data.number).toEqual(1.2)
  229. trim.value = ' hello, world '
  230. triggerEvent('input', trim)
  231. await nextTick()
  232. expect(data.trim).toEqual('hello, world')
  233. lazy.value = 'foo'
  234. triggerEvent('change', lazy)
  235. await nextTick()
  236. expect(data.lazy).toEqual('foo')
  237. })
  238. it('should work with checkbox', async () => {
  239. const component = defineComponent({
  240. data() {
  241. return { value: null }
  242. },
  243. render() {
  244. return [
  245. withVModel(
  246. h('input', {
  247. type: 'checkbox',
  248. 'onUpdate:modelValue': setValue.bind(this)
  249. }),
  250. this.value
  251. )
  252. ]
  253. }
  254. })
  255. render(h(component), root)
  256. const input = root.querySelector('input')
  257. const data = root._vnode.component.data
  258. input.checked = true
  259. triggerEvent('change', input)
  260. await nextTick()
  261. expect(data.value).toEqual(true)
  262. data.value = false
  263. await nextTick()
  264. expect(input.checked).toEqual(false)
  265. data.value = true
  266. await nextTick()
  267. expect(input.checked).toEqual(true)
  268. input.checked = false
  269. triggerEvent('change', input)
  270. await nextTick()
  271. expect(data.value).toEqual(false)
  272. })
  273. it('should work with checkbox and true-value/false-value', async () => {
  274. const component = defineComponent({
  275. data() {
  276. return { value: 'yes' }
  277. },
  278. render() {
  279. return [
  280. withVModel(
  281. h('input', {
  282. type: 'checkbox',
  283. 'true-value': 'yes',
  284. 'false-value': 'no',
  285. 'onUpdate:modelValue': setValue.bind(this)
  286. }),
  287. this.value
  288. )
  289. ]
  290. }
  291. })
  292. render(h(component), root)
  293. const input = root.querySelector('input')
  294. const data = root._vnode.component.data
  295. // DOM checked state should respect initial true-value/false-value
  296. expect(input.checked).toEqual(true)
  297. input.checked = false
  298. triggerEvent('change', input)
  299. await nextTick()
  300. expect(data.value).toEqual('no')
  301. data.value = 'yes'
  302. await nextTick()
  303. expect(input.checked).toEqual(true)
  304. data.value = 'no'
  305. await nextTick()
  306. expect(input.checked).toEqual(false)
  307. input.checked = true
  308. triggerEvent('change', input)
  309. await nextTick()
  310. expect(data.value).toEqual('yes')
  311. })
  312. it('should work with checkbox and true-value/false-value with object values', async () => {
  313. const component = defineComponent({
  314. data() {
  315. return { value: null }
  316. },
  317. render() {
  318. return [
  319. withVModel(
  320. h('input', {
  321. type: 'checkbox',
  322. 'true-value': { yes: 'yes' },
  323. 'false-value': { no: 'no' },
  324. 'onUpdate:modelValue': setValue.bind(this)
  325. }),
  326. this.value
  327. )
  328. ]
  329. }
  330. })
  331. render(h(component), root)
  332. const input = root.querySelector('input')
  333. const data = root._vnode.component.data
  334. input.checked = true
  335. triggerEvent('change', input)
  336. await nextTick()
  337. expect(data.value).toEqual({ yes: 'yes' })
  338. data.value = { no: 'no' }
  339. await nextTick()
  340. expect(input.checked).toEqual(false)
  341. data.value = { yes: 'yes' }
  342. await nextTick()
  343. expect(input.checked).toEqual(true)
  344. input.checked = false
  345. triggerEvent('change', input)
  346. await nextTick()
  347. expect(data.value).toEqual({ no: 'no' })
  348. })
  349. it(`should support array as a checkbox model`, async () => {
  350. const component = defineComponent({
  351. data() {
  352. return { value: [] }
  353. },
  354. render() {
  355. return [
  356. withVModel(
  357. h('input', {
  358. type: 'checkbox',
  359. class: 'foo',
  360. value: 'foo',
  361. 'onUpdate:modelValue': setValue.bind(this)
  362. }),
  363. this.value
  364. ),
  365. withVModel(
  366. h('input', {
  367. type: 'checkbox',
  368. class: 'bar',
  369. value: 'bar',
  370. 'onUpdate:modelValue': setValue.bind(this)
  371. }),
  372. this.value
  373. )
  374. ]
  375. }
  376. })
  377. render(h(component), root)
  378. const foo = root.querySelector('.foo')
  379. const bar = root.querySelector('.bar')
  380. const data = root._vnode.component.data
  381. foo.checked = true
  382. triggerEvent('change', foo)
  383. await nextTick()
  384. expect(data.value).toMatchObject(['foo'])
  385. bar.checked = true
  386. triggerEvent('change', bar)
  387. await nextTick()
  388. expect(data.value).toMatchObject(['foo', 'bar'])
  389. bar.checked = false
  390. triggerEvent('change', bar)
  391. await nextTick()
  392. expect(data.value).toMatchObject(['foo'])
  393. foo.checked = false
  394. triggerEvent('change', foo)
  395. await nextTick()
  396. expect(data.value).toMatchObject([])
  397. data.value = ['foo']
  398. await nextTick()
  399. expect(bar.checked).toEqual(false)
  400. expect(foo.checked).toEqual(true)
  401. data.value = ['bar']
  402. await nextTick()
  403. expect(foo.checked).toEqual(false)
  404. expect(bar.checked).toEqual(true)
  405. data.value = []
  406. await nextTick()
  407. expect(foo.checked).toEqual(false)
  408. expect(bar.checked).toEqual(false)
  409. })
  410. it(`should support Set as a checkbox model`, async () => {
  411. const component = defineComponent({
  412. data() {
  413. return { value: new Set() }
  414. },
  415. render() {
  416. return [
  417. withVModel(
  418. h('input', {
  419. type: 'checkbox',
  420. class: 'foo',
  421. value: 'foo',
  422. 'onUpdate:modelValue': setValue.bind(this)
  423. }),
  424. this.value
  425. ),
  426. withVModel(
  427. h('input', {
  428. type: 'checkbox',
  429. class: 'bar',
  430. value: 'bar',
  431. 'onUpdate:modelValue': setValue.bind(this)
  432. }),
  433. this.value
  434. )
  435. ]
  436. }
  437. })
  438. render(h(component), root)
  439. const foo = root.querySelector('.foo')
  440. const bar = root.querySelector('.bar')
  441. const data = root._vnode.component.data
  442. foo.checked = true
  443. triggerEvent('change', foo)
  444. await nextTick()
  445. expect(data.value).toMatchObject(new Set(['foo']))
  446. bar.checked = true
  447. triggerEvent('change', bar)
  448. await nextTick()
  449. expect(data.value).toMatchObject(new Set(['foo', 'bar']))
  450. bar.checked = false
  451. triggerEvent('change', bar)
  452. await nextTick()
  453. expect(data.value).toMatchObject(new Set(['foo']))
  454. foo.checked = false
  455. triggerEvent('change', foo)
  456. await nextTick()
  457. expect(data.value).toMatchObject(new Set())
  458. data.value = new Set(['foo'])
  459. await nextTick()
  460. expect(bar.checked).toEqual(false)
  461. expect(foo.checked).toEqual(true)
  462. data.value = new Set(['bar'])
  463. await nextTick()
  464. expect(foo.checked).toEqual(false)
  465. expect(bar.checked).toEqual(true)
  466. data.value = new Set()
  467. await nextTick()
  468. expect(foo.checked).toEqual(false)
  469. expect(bar.checked).toEqual(false)
  470. })
  471. it('should work with radio', async () => {
  472. const component = defineComponent({
  473. data() {
  474. return { value: null }
  475. },
  476. render() {
  477. return [
  478. withVModel(
  479. h('input', {
  480. type: 'radio',
  481. class: 'foo',
  482. value: 'foo',
  483. 'onUpdate:modelValue': setValue.bind(this)
  484. }),
  485. this.value
  486. ),
  487. withVModel(
  488. h('input', {
  489. type: 'radio',
  490. class: 'bar',
  491. value: 'bar',
  492. 'onUpdate:modelValue': setValue.bind(this)
  493. }),
  494. this.value
  495. )
  496. ]
  497. }
  498. })
  499. render(h(component), root)
  500. const foo = root.querySelector('.foo')
  501. const bar = root.querySelector('.bar')
  502. const data = root._vnode.component.data
  503. foo.checked = true
  504. triggerEvent('change', foo)
  505. await nextTick()
  506. expect(data.value).toEqual('foo')
  507. bar.checked = true
  508. triggerEvent('change', bar)
  509. await nextTick()
  510. expect(data.value).toEqual('bar')
  511. data.value = null
  512. await nextTick()
  513. expect(foo.checked).toEqual(false)
  514. expect(bar.checked).toEqual(false)
  515. data.value = 'foo'
  516. await nextTick()
  517. expect(foo.checked).toEqual(true)
  518. expect(bar.checked).toEqual(false)
  519. data.value = 'bar'
  520. await nextTick()
  521. expect(foo.checked).toEqual(false)
  522. expect(bar.checked).toEqual(true)
  523. })
  524. it('should work with single select', async () => {
  525. const component = defineComponent({
  526. data() {
  527. return { value: null }
  528. },
  529. render() {
  530. return [
  531. withVModel(
  532. h(
  533. 'select',
  534. {
  535. value: null,
  536. 'onUpdate:modelValue': setValue.bind(this)
  537. },
  538. [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
  539. ),
  540. this.value
  541. )
  542. ]
  543. }
  544. })
  545. render(h(component), root)
  546. const input = root.querySelector('select')
  547. const foo = root.querySelector('option[value=foo]')
  548. const bar = root.querySelector('option[value=bar]')
  549. const data = root._vnode.component.data
  550. foo.selected = true
  551. triggerEvent('change', input)
  552. await nextTick()
  553. expect(data.value).toEqual('foo')
  554. foo.selected = false
  555. bar.selected = true
  556. triggerEvent('change', input)
  557. await nextTick()
  558. expect(data.value).toEqual('bar')
  559. foo.selected = false
  560. bar.selected = false
  561. data.value = 'foo'
  562. await nextTick()
  563. expect(input.value).toEqual('foo')
  564. expect(foo.selected).toEqual(true)
  565. expect(bar.selected).toEqual(false)
  566. foo.selected = true
  567. bar.selected = false
  568. data.value = 'bar'
  569. await nextTick()
  570. expect(input.value).toEqual('bar')
  571. expect(foo.selected).toEqual(false)
  572. expect(bar.selected).toEqual(true)
  573. })
  574. it('multiple select (model is Array)', async () => {
  575. const component = defineComponent({
  576. data() {
  577. return { value: [] }
  578. },
  579. render() {
  580. return [
  581. withVModel(
  582. h(
  583. 'select',
  584. {
  585. value: null,
  586. multiple: true,
  587. 'onUpdate:modelValue': setValue.bind(this)
  588. },
  589. [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
  590. ),
  591. this.value
  592. )
  593. ]
  594. }
  595. })
  596. render(h(component), root)
  597. const input = root.querySelector('select')
  598. const foo = root.querySelector('option[value=foo]')
  599. const bar = root.querySelector('option[value=bar]')
  600. const data = root._vnode.component.data
  601. foo.selected = true
  602. triggerEvent('change', input)
  603. await nextTick()
  604. expect(data.value).toMatchObject(['foo'])
  605. foo.selected = false
  606. bar.selected = true
  607. triggerEvent('change', input)
  608. await nextTick()
  609. expect(data.value).toMatchObject(['bar'])
  610. foo.selected = true
  611. bar.selected = true
  612. triggerEvent('change', input)
  613. await nextTick()
  614. expect(data.value).toMatchObject(['foo', 'bar'])
  615. foo.selected = false
  616. bar.selected = false
  617. data.value = ['foo']
  618. await nextTick()
  619. expect(input.value).toEqual('foo')
  620. expect(foo.selected).toEqual(true)
  621. expect(bar.selected).toEqual(false)
  622. foo.selected = false
  623. bar.selected = false
  624. data.value = ['foo', 'bar']
  625. await nextTick()
  626. expect(foo.selected).toEqual(true)
  627. expect(bar.selected).toEqual(true)
  628. })
  629. it('v-model.number should work with select tag', async () => {
  630. const component = defineComponent({
  631. data() {
  632. return { value: null }
  633. },
  634. render() {
  635. return [
  636. withVModel(
  637. h(
  638. 'select',
  639. {
  640. value: null,
  641. 'onUpdate:modelValue': setValue.bind(this)
  642. },
  643. [h('option', { value: '1' }), h('option', { value: '2' })]
  644. ),
  645. this.value,
  646. {
  647. number: true
  648. }
  649. )
  650. ]
  651. }
  652. })
  653. render(h(component), root)
  654. const input = root.querySelector('select')
  655. const one = root.querySelector('option[value="1"]')
  656. const data = root._vnode.component.data
  657. one.selected = true
  658. triggerEvent('change', input)
  659. await nextTick()
  660. expect(typeof data.value).toEqual('number')
  661. expect(data.value).toEqual(1)
  662. })
  663. it('v-model.number should work with multiple select', async () => {
  664. const component = defineComponent({
  665. data() {
  666. return { value: [] }
  667. },
  668. render() {
  669. return [
  670. withVModel(
  671. h(
  672. 'select',
  673. {
  674. value: null,
  675. multiple: true,
  676. 'onUpdate:modelValue': setValue.bind(this)
  677. },
  678. [h('option', { value: '1' }), h('option', { value: '2' })]
  679. ),
  680. this.value,
  681. {
  682. number: true
  683. }
  684. )
  685. ]
  686. }
  687. })
  688. render(h(component), root)
  689. const input = root.querySelector('select')
  690. const one = root.querySelector('option[value="1"]')
  691. const two = root.querySelector('option[value="2"]')
  692. const data = root._vnode.component.data
  693. one.selected = true
  694. two.selected = false
  695. triggerEvent('change', input)
  696. await nextTick()
  697. expect(data.value).toMatchObject([1])
  698. one.selected = false
  699. two.selected = true
  700. triggerEvent('change', input)
  701. await nextTick()
  702. expect(data.value).toMatchObject([2])
  703. one.selected = true
  704. two.selected = true
  705. triggerEvent('change', input)
  706. await nextTick()
  707. expect(data.value).toMatchObject([1, 2])
  708. one.selected = false
  709. two.selected = false
  710. data.value = [1]
  711. await nextTick()
  712. expect(one.selected).toEqual(true)
  713. expect(two.selected).toEqual(false)
  714. one.selected = false
  715. two.selected = false
  716. data.value = [1, 2]
  717. await nextTick()
  718. expect(one.selected).toEqual(true)
  719. expect(two.selected).toEqual(true)
  720. })
  721. it('multiple select (model is Array, option value is object)', async () => {
  722. const fooValue = { foo: 1 }
  723. const barValue = { bar: 1 }
  724. const component = defineComponent({
  725. data() {
  726. return { value: [] }
  727. },
  728. render() {
  729. return [
  730. withVModel(
  731. h(
  732. 'select',
  733. {
  734. value: null,
  735. multiple: true,
  736. 'onUpdate:modelValue': setValue.bind(this)
  737. },
  738. [
  739. h('option', { value: fooValue }),
  740. h('option', { value: barValue })
  741. ]
  742. ),
  743. this.value
  744. )
  745. ]
  746. }
  747. })
  748. render(h(component), root)
  749. await nextTick()
  750. const input = root.querySelector('select')
  751. const [foo, bar] = root.querySelectorAll('option')
  752. const data = root._vnode.component.data
  753. foo.selected = true
  754. triggerEvent('change', input)
  755. await nextTick()
  756. expect(data.value).toMatchObject([fooValue])
  757. foo.selected = false
  758. bar.selected = true
  759. triggerEvent('change', input)
  760. await nextTick()
  761. expect(data.value).toMatchObject([barValue])
  762. foo.selected = true
  763. bar.selected = true
  764. triggerEvent('change', input)
  765. await nextTick()
  766. expect(data.value).toMatchObject([fooValue, barValue])
  767. foo.selected = false
  768. bar.selected = false
  769. data.value = [fooValue, barValue]
  770. await nextTick()
  771. expect(foo.selected).toEqual(true)
  772. expect(bar.selected).toEqual(true)
  773. foo.selected = false
  774. bar.selected = false
  775. data.value = [{ foo: 1 }, { bar: 1 }]
  776. await nextTick()
  777. // looseEqual
  778. expect(foo.selected).toEqual(true)
  779. expect(bar.selected).toEqual(true)
  780. })
  781. it('multiple select (model is Set)', async () => {
  782. const component = defineComponent({
  783. data() {
  784. return { value: new Set() }
  785. },
  786. render() {
  787. return [
  788. withVModel(
  789. h(
  790. 'select',
  791. {
  792. value: null,
  793. multiple: true,
  794. 'onUpdate:modelValue': setValue.bind(this)
  795. },
  796. [h('option', { value: 'foo' }), h('option', { value: 'bar' })]
  797. ),
  798. this.value
  799. )
  800. ]
  801. }
  802. })
  803. render(h(component), root)
  804. const input = root.querySelector('select')
  805. const foo = root.querySelector('option[value=foo]')
  806. const bar = root.querySelector('option[value=bar]')
  807. const data = root._vnode.component.data
  808. foo.selected = true
  809. triggerEvent('change', input)
  810. await nextTick()
  811. expect(data.value).toBeInstanceOf(Set)
  812. expect(data.value).toMatchObject(new Set(['foo']))
  813. foo.selected = false
  814. bar.selected = true
  815. triggerEvent('change', input)
  816. await nextTick()
  817. expect(data.value).toBeInstanceOf(Set)
  818. expect(data.value).toMatchObject(new Set(['bar']))
  819. foo.selected = true
  820. bar.selected = true
  821. triggerEvent('change', input)
  822. await nextTick()
  823. expect(data.value).toBeInstanceOf(Set)
  824. expect(data.value).toMatchObject(new Set(['foo', 'bar']))
  825. foo.selected = false
  826. bar.selected = false
  827. data.value = new Set(['foo'])
  828. await nextTick()
  829. expect(input.value).toEqual('foo')
  830. expect(foo.selected).toEqual(true)
  831. expect(bar.selected).toEqual(false)
  832. foo.selected = false
  833. bar.selected = false
  834. data.value = new Set(['foo', 'bar'])
  835. await nextTick()
  836. expect(foo.selected).toEqual(true)
  837. expect(bar.selected).toEqual(true)
  838. })
  839. it('multiple select (model is Set, option value is object)', async () => {
  840. const fooValue = { foo: 1 }
  841. const barValue = { bar: 1 }
  842. const component = defineComponent({
  843. data() {
  844. return { value: new Set() }
  845. },
  846. render() {
  847. return [
  848. withVModel(
  849. h(
  850. 'select',
  851. {
  852. value: null,
  853. multiple: true,
  854. 'onUpdate:modelValue': setValue.bind(this)
  855. },
  856. [
  857. h('option', { value: fooValue }),
  858. h('option', { value: barValue })
  859. ]
  860. ),
  861. this.value
  862. )
  863. ]
  864. }
  865. })
  866. render(h(component), root)
  867. await nextTick()
  868. const input = root.querySelector('select')
  869. const [foo, bar] = root.querySelectorAll('option')
  870. const data = root._vnode.component.data
  871. foo.selected = true
  872. triggerEvent('change', input)
  873. await nextTick()
  874. expect(data.value).toMatchObject(new Set([fooValue]))
  875. foo.selected = false
  876. bar.selected = true
  877. triggerEvent('change', input)
  878. await nextTick()
  879. expect(data.value).toMatchObject(new Set([barValue]))
  880. foo.selected = true
  881. bar.selected = true
  882. triggerEvent('change', input)
  883. await nextTick()
  884. expect(data.value).toMatchObject(new Set([fooValue, barValue]))
  885. foo.selected = false
  886. bar.selected = false
  887. data.value = new Set([fooValue, barValue])
  888. await nextTick()
  889. expect(foo.selected).toEqual(true)
  890. expect(bar.selected).toEqual(true)
  891. foo.selected = false
  892. bar.selected = false
  893. data.value = new Set([{ foo: 1 }, { bar: 1 }])
  894. await nextTick()
  895. // whithout looseEqual, here is different from Array
  896. expect(foo.selected).toEqual(false)
  897. expect(bar.selected).toEqual(false)
  898. })
  899. it('should work with composition session', async () => {
  900. const component = defineComponent({
  901. data() {
  902. return { value: '' }
  903. },
  904. render() {
  905. return [
  906. withVModel(
  907. h('input', {
  908. 'onUpdate:modelValue': setValue.bind(this)
  909. }),
  910. this.value
  911. )
  912. ]
  913. }
  914. })
  915. render(h(component), root)
  916. const input = root.querySelector('input')!
  917. const data = root._vnode.component.data
  918. expect(input.value).toEqual('')
  919. //developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event
  920. //compositionstart event could be fired after a user starts entering a Chinese character using a Pinyin IME
  921. input.value = '使用拼音'
  922. triggerEvent('compositionstart', input)
  923. await nextTick()
  924. expect(data.value).toEqual('')
  925. // input event has no effect during composition session
  926. input.value = '使用拼音输入'
  927. triggerEvent('input', input)
  928. await nextTick()
  929. expect(data.value).toEqual('')
  930. // After compositionend event being fired, an input event will be automatically trigger
  931. triggerEvent('compositionend', input)
  932. await nextTick()
  933. expect(data.value).toEqual('使用拼音输入')
  934. })
  935. })