on.spec.ts 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211
  1. import Vue from 'vue'
  2. import { supportsPassive } from 'core/util/env'
  3. import { SpyInstanceFn } from 'vitest'
  4. describe('Directive v-on', () => {
  5. let vm, spy: SpyInstanceFn, el: HTMLElement
  6. beforeEach(() => {
  7. vm = null
  8. spy = vi.fn()
  9. el = document.createElement('div')
  10. document.body.appendChild(el)
  11. })
  12. afterEach(() => {
  13. if (vm) {
  14. document.body.removeChild(vm.$el)
  15. }
  16. })
  17. it('should bind event to a method', () => {
  18. vm = new Vue({
  19. el,
  20. template: '<div v-on:click="foo"></div>',
  21. methods: { foo: spy }
  22. })
  23. triggerEvent(vm.$el, 'click')
  24. expect(spy.mock.calls.length).toBe(1)
  25. const args = spy.mock.calls
  26. const event = (args[0] && args[0][0]) || {}
  27. expect(event.type).toBe('click')
  28. })
  29. it('should bind event to an inline statement', () => {
  30. vm = new Vue({
  31. el,
  32. template: '<div v-on:click="foo(1,2,3,$event)"></div>',
  33. methods: { foo: spy }
  34. })
  35. triggerEvent(vm.$el, 'click')
  36. expect(spy.mock.calls.length).toBe(1)
  37. const args = spy.mock.calls
  38. const firstArgs = args[0]
  39. expect(firstArgs.length).toBe(4)
  40. expect(firstArgs[0]).toBe(1)
  41. expect(firstArgs[1]).toBe(2)
  42. expect(firstArgs[2]).toBe(3)
  43. expect(firstArgs[3].type).toBe('click')
  44. })
  45. it('should support inline function expression', () => {
  46. const spy = vi.fn()
  47. vm = new Vue({
  48. el,
  49. template: `<div class="test" @click="function (e) { log(e.target.className) }"></div>`,
  50. methods: {
  51. log: spy
  52. }
  53. }).$mount()
  54. triggerEvent(vm.$el, 'click')
  55. expect(spy).toHaveBeenCalledWith('test')
  56. })
  57. it('should support shorthand', () => {
  58. vm = new Vue({
  59. el,
  60. template: '<a href="#test" @click.prevent="foo"></a>',
  61. methods: { foo: spy }
  62. })
  63. triggerEvent(vm.$el, 'click')
  64. expect(spy.mock.calls.length).toBe(1)
  65. })
  66. it('should support stop propagation', () => {
  67. vm = new Vue({
  68. el,
  69. template: `
  70. <div @click.stop="foo"></div>
  71. `,
  72. methods: { foo: spy }
  73. })
  74. const hash = window.location.hash
  75. triggerEvent(vm.$el, 'click')
  76. expect(window.location.hash).toBe(hash)
  77. })
  78. it('should support prevent default', () => {
  79. vm = new Vue({
  80. el,
  81. template: `
  82. <input type="checkbox" ref="input" @click.prevent="foo">
  83. `,
  84. methods: {
  85. foo($event) {
  86. spy($event.defaultPrevented)
  87. }
  88. }
  89. })
  90. vm.$refs.input.checked = false
  91. triggerEvent(vm.$refs.input, 'click')
  92. expect(spy).toHaveBeenCalledWith(true)
  93. })
  94. it('should support capture', () => {
  95. const callOrder: any[] = []
  96. vm = new Vue({
  97. el,
  98. template: `
  99. <div @click.capture="foo">
  100. <div @click="bar"></div>
  101. </div>
  102. `,
  103. methods: {
  104. foo() {
  105. callOrder.push(1)
  106. },
  107. bar() {
  108. callOrder.push(2)
  109. }
  110. }
  111. })
  112. triggerEvent(vm.$el.firstChild, 'click')
  113. expect(callOrder.toString()).toBe('1,2')
  114. })
  115. it('should support once', () => {
  116. vm = new Vue({
  117. el,
  118. template: `
  119. <div @click.once="foo">
  120. </div>
  121. `,
  122. methods: { foo: spy }
  123. })
  124. triggerEvent(vm.$el, 'click')
  125. expect(spy.mock.calls.length).toBe(1)
  126. triggerEvent(vm.$el, 'click')
  127. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  128. })
  129. // #4655
  130. it('should handle .once on multiple elements properly', () => {
  131. vm = new Vue({
  132. el,
  133. template: `
  134. <div>
  135. <button ref="one" @click.once="foo">one</button>
  136. <button ref="two" @click.once="foo">two</button>
  137. </div>
  138. `,
  139. methods: { foo: spy }
  140. })
  141. triggerEvent(vm.$refs.one, 'click')
  142. expect(spy.mock.calls.length).toBe(1)
  143. triggerEvent(vm.$refs.one, 'click')
  144. expect(spy.mock.calls.length).toBe(1)
  145. triggerEvent(vm.$refs.two, 'click')
  146. expect(spy.mock.calls.length).toBe(2)
  147. triggerEvent(vm.$refs.one, 'click')
  148. triggerEvent(vm.$refs.two, 'click')
  149. expect(spy.mock.calls.length).toBe(2)
  150. })
  151. it('should support capture and once', () => {
  152. const callOrder: any[] = []
  153. vm = new Vue({
  154. el,
  155. template: `
  156. <div @click.capture.once="foo">
  157. <div @click="bar"></div>
  158. </div>
  159. `,
  160. methods: {
  161. foo() {
  162. callOrder.push(1)
  163. },
  164. bar() {
  165. callOrder.push(2)
  166. }
  167. }
  168. })
  169. triggerEvent(vm.$el.firstChild, 'click')
  170. expect(callOrder.toString()).toBe('1,2')
  171. triggerEvent(vm.$el.firstChild, 'click')
  172. expect(callOrder.toString()).toBe('1,2,2')
  173. })
  174. // #4846
  175. it('should support once and other modifiers', () => {
  176. vm = new Vue({
  177. el,
  178. template: `<div @click.once.self="foo"><span/></div>`,
  179. methods: { foo: spy }
  180. })
  181. triggerEvent(vm.$el.firstChild, 'click')
  182. expect(spy).not.toHaveBeenCalled()
  183. triggerEvent(vm.$el, 'click')
  184. expect(spy).toHaveBeenCalled()
  185. triggerEvent(vm.$el, 'click')
  186. expect(spy.mock.calls.length).toBe(1)
  187. })
  188. it('should support keyCode', () => {
  189. vm = new Vue({
  190. el,
  191. template: `<input @keyup.enter="foo">`,
  192. methods: { foo: spy }
  193. })
  194. triggerEvent(vm.$el, 'keyup', e => {
  195. e.keyCode = 13
  196. })
  197. expect(spy).toHaveBeenCalled()
  198. })
  199. it('should support automatic key name inference', () => {
  200. vm = new Vue({
  201. el,
  202. template: `<input @keyup.arrow-right="foo">`,
  203. methods: { foo: spy }
  204. })
  205. triggerEvent(vm.$el, 'keyup', e => {
  206. e.key = 'ArrowRight'
  207. })
  208. expect(spy).toHaveBeenCalled()
  209. })
  210. // ctrl, shift, alt, meta
  211. it('should support system modifiers', () => {
  212. vm = new Vue({
  213. el,
  214. template: `
  215. <div>
  216. <input ref="ctrl" @keyup.ctrl="foo">
  217. <input ref="shift" @keyup.shift="foo">
  218. <input ref="alt" @keyup.alt="foo">
  219. <input ref="meta" @keyup.meta="foo">
  220. </div>
  221. `,
  222. methods: { foo: spy }
  223. })
  224. triggerEvent(vm.$refs.ctrl, 'keyup')
  225. expect(spy.mock.calls.length).toBe(0)
  226. triggerEvent(vm.$refs.ctrl, 'keyup', e => {
  227. e.ctrlKey = true
  228. })
  229. expect(spy.mock.calls.length).toBe(1)
  230. triggerEvent(vm.$refs.shift, 'keyup')
  231. expect(spy.mock.calls.length).toBe(1)
  232. triggerEvent(vm.$refs.shift, 'keyup', e => {
  233. e.shiftKey = true
  234. })
  235. expect(spy.mock.calls.length).toBe(2)
  236. triggerEvent(vm.$refs.alt, 'keyup')
  237. expect(spy.mock.calls.length).toBe(2)
  238. triggerEvent(vm.$refs.alt, 'keyup', e => {
  239. e.altKey = true
  240. })
  241. expect(spy.mock.calls.length).toBe(3)
  242. triggerEvent(vm.$refs.meta, 'keyup')
  243. expect(spy.mock.calls.length).toBe(3)
  244. triggerEvent(vm.$refs.meta, 'keyup', e => {
  245. e.metaKey = true
  246. })
  247. expect(spy.mock.calls.length).toBe(4)
  248. })
  249. it('should support exact modifier', () => {
  250. vm = new Vue({
  251. el,
  252. template: `
  253. <div>
  254. <input ref="ctrl" @keyup.exact="foo">
  255. </div>
  256. `,
  257. methods: { foo: spy }
  258. })
  259. triggerEvent(vm.$refs.ctrl, 'keyup')
  260. expect(spy.mock.calls.length).toBe(1)
  261. triggerEvent(vm.$refs.ctrl, 'keyup', e => {
  262. e.ctrlKey = true
  263. })
  264. expect(spy.mock.calls.length).toBe(1)
  265. // should not trigger if has other system modifiers
  266. triggerEvent(vm.$refs.ctrl, 'keyup', e => {
  267. e.ctrlKey = true
  268. e.altKey = true
  269. })
  270. expect(spy.mock.calls.length).toBe(1)
  271. })
  272. it('should support system modifiers with exact', () => {
  273. vm = new Vue({
  274. el,
  275. template: `
  276. <div>
  277. <input ref="ctrl" @keyup.ctrl.exact="foo">
  278. </div>
  279. `,
  280. methods: { foo: spy }
  281. })
  282. triggerEvent(vm.$refs.ctrl, 'keyup')
  283. expect(spy.mock.calls.length).toBe(0)
  284. triggerEvent(vm.$refs.ctrl, 'keyup', e => {
  285. e.ctrlKey = true
  286. })
  287. expect(spy.mock.calls.length).toBe(1)
  288. // should not trigger if has other system modifiers
  289. triggerEvent(vm.$refs.ctrl, 'keyup', e => {
  290. e.ctrlKey = true
  291. e.altKey = true
  292. })
  293. expect(spy.mock.calls.length).toBe(1)
  294. })
  295. it('should support number keyCode', () => {
  296. vm = new Vue({
  297. el,
  298. template: `<input @keyup.13="foo">`,
  299. methods: { foo: spy }
  300. })
  301. triggerEvent(vm.$el, 'keyup', e => {
  302. e.keyCode = 13
  303. })
  304. expect(spy).toHaveBeenCalled()
  305. })
  306. it('should support mouse modifier', () => {
  307. const left = 0
  308. const middle = 1
  309. const right = 2
  310. const spyLeft = vi.fn()
  311. const spyMiddle = vi.fn()
  312. const spyRight = vi.fn()
  313. vm = new Vue({
  314. el,
  315. template: `
  316. <div>
  317. <div ref="left" @mousedown.left="foo">left</div>
  318. <div ref="right" @mousedown.right="foo1">right</div>
  319. <div ref="middle" @mousedown.middle="foo2">right</div>
  320. </div>
  321. `,
  322. methods: {
  323. foo: spyLeft,
  324. foo1: spyRight,
  325. foo2: spyMiddle
  326. }
  327. })
  328. triggerEvent(vm.$refs.left, 'mousedown', e => {
  329. e.button = right
  330. })
  331. triggerEvent(vm.$refs.left, 'mousedown', e => {
  332. e.button = middle
  333. })
  334. expect(spyLeft).not.toHaveBeenCalled()
  335. triggerEvent(vm.$refs.left, 'mousedown', e => {
  336. e.button = left
  337. })
  338. expect(spyLeft).toHaveBeenCalled()
  339. triggerEvent(vm.$refs.right, 'mousedown', e => {
  340. e.button = left
  341. })
  342. triggerEvent(vm.$refs.right, 'mousedown', e => {
  343. e.button = middle
  344. })
  345. expect(spyRight).not.toHaveBeenCalled()
  346. triggerEvent(vm.$refs.right, 'mousedown', e => {
  347. e.button = right
  348. })
  349. expect(spyRight).toHaveBeenCalled()
  350. triggerEvent(vm.$refs.middle, 'mousedown', e => {
  351. e.button = left
  352. })
  353. triggerEvent(vm.$refs.middle, 'mousedown', e => {
  354. e.button = right
  355. })
  356. expect(spyMiddle).not.toHaveBeenCalled()
  357. triggerEvent(vm.$refs.middle, 'mousedown', e => {
  358. e.button = middle
  359. })
  360. expect(spyMiddle).toHaveBeenCalled()
  361. })
  362. it('should support KeyboardEvent.key for built in aliases', () => {
  363. vm = new Vue({
  364. el,
  365. template: `
  366. <div>
  367. <input ref="enter" @keyup.enter="foo">
  368. <input ref="space" @keyup.space="foo">
  369. <input ref="esc" @keyup.esc="foo">
  370. <input ref="left" @keyup.left="foo">
  371. <input ref="delete" @keyup.delete="foo">
  372. </div>
  373. `,
  374. methods: { foo: spy }
  375. })
  376. triggerEvent(vm.$refs.enter, 'keyup', e => {
  377. e.key = 'Enter'
  378. })
  379. expect(spy.mock.calls.length).toBe(1)
  380. triggerEvent(vm.$refs.space, 'keyup', e => {
  381. e.key = ' '
  382. })
  383. expect(spy.mock.calls.length).toBe(2)
  384. triggerEvent(vm.$refs.esc, 'keyup', e => {
  385. e.key = 'Escape'
  386. })
  387. expect(spy.mock.calls.length).toBe(3)
  388. triggerEvent(vm.$refs.left, 'keyup', e => {
  389. e.key = 'ArrowLeft'
  390. })
  391. expect(spy.mock.calls.length).toBe(4)
  392. triggerEvent(vm.$refs.delete, 'keyup', e => {
  393. e.key = 'Backspace'
  394. })
  395. expect(spy.mock.calls.length).toBe(5)
  396. triggerEvent(vm.$refs.delete, 'keyup', e => {
  397. e.key = 'Delete'
  398. })
  399. expect(spy.mock.calls.length).toBe(6)
  400. })
  401. it('should support custom keyCode', () => {
  402. Vue.config.keyCodes.test = 1
  403. vm = new Vue({
  404. el,
  405. template: `<input @keyup.test="foo">`,
  406. methods: { foo: spy }
  407. })
  408. triggerEvent(vm.$el, 'keyup', e => {
  409. e.keyCode = 1
  410. })
  411. expect(spy).toHaveBeenCalled()
  412. Vue.config.keyCodes = Object.create(null)
  413. })
  414. it('should override built-in keyCode', () => {
  415. Vue.config.keyCodes.up = [1, 87]
  416. vm = new Vue({
  417. el,
  418. template: `<input @keyup.up="foo" @keyup.down="foo">`,
  419. methods: { foo: spy }
  420. })
  421. triggerEvent(vm.$el, 'keyup', e => {
  422. e.keyCode = 87
  423. })
  424. expect(spy).toHaveBeenCalled()
  425. triggerEvent(vm.$el, 'keyup', e => {
  426. e.keyCode = 1
  427. })
  428. expect(spy).toHaveBeenCalledTimes(2)
  429. // should not affect built-in down keycode
  430. triggerEvent(vm.$el, 'keyup', e => {
  431. e.keyCode = 40
  432. })
  433. expect(spy).toHaveBeenCalledTimes(3)
  434. Vue.config.keyCodes = Object.create(null)
  435. })
  436. it('should bind to a child component', () => {
  437. vm = new Vue({
  438. el,
  439. template: '<bar @custom="foo"></bar>',
  440. methods: { foo: spy },
  441. components: {
  442. bar: {
  443. template: '<span>Hello</span>'
  444. }
  445. }
  446. })
  447. vm.$children[0].$emit('custom', 'foo', 'bar')
  448. expect(spy).toHaveBeenCalledWith('foo', 'bar')
  449. })
  450. it('should be able to bind native events for a child component', () => {
  451. vm = new Vue({
  452. el,
  453. template: '<bar @click.native="foo"></bar>',
  454. methods: { foo: spy },
  455. components: {
  456. bar: {
  457. template: '<span>Hello</span>'
  458. }
  459. }
  460. })
  461. vm.$children[0].$emit('click')
  462. expect(spy).not.toHaveBeenCalled()
  463. triggerEvent(vm.$children[0].$el, 'click')
  464. expect(spy).toHaveBeenCalled()
  465. })
  466. it('should throw a warning if native modifier is used on native HTML element', () => {
  467. vm = new Vue({
  468. el,
  469. template: `
  470. <button @click.native="foo"></button>
  471. `,
  472. methods: { foo: spy }
  473. })
  474. triggerEvent(vm.$el, 'click')
  475. expect(
  476. `The .native modifier for v-on is only valid on components but it was used on <button>.`
  477. ).toHaveBeenWarned()
  478. expect(spy.mock.calls.length).toBe(0)
  479. })
  480. it('should not throw a warning if native modifier is used on a dynamic component', () => {
  481. vm = new Vue({
  482. el,
  483. template: `
  484. <component is="div" @click.native="foo('native')" @click="foo('regular')"/>
  485. `,
  486. methods: { foo: spy }
  487. })
  488. triggerEvent(vm.$el, 'click')
  489. expect(
  490. `The .native modifier for v-on is only valid on components but it was used on <div>.`
  491. ).not.toHaveBeenWarned()
  492. expect(spy.mock.calls).toEqual([['regular']]) // Regular @click should work for dynamic components resolved to native HTML elements.
  493. })
  494. it('.once modifier should work with child components', () => {
  495. vm = new Vue({
  496. el,
  497. template: '<bar @custom.once="foo"></bar>',
  498. methods: { foo: spy },
  499. components: {
  500. bar: {
  501. template: '<span>Hello</span>'
  502. }
  503. }
  504. })
  505. vm.$children[0].$emit('custom')
  506. expect(spy.mock.calls.length).toBe(1)
  507. vm.$children[0].$emit('custom')
  508. expect(spy.mock.calls.length).toBe(1) // should not be called again
  509. })
  510. it('remove listener', done => {
  511. const spy2 = vi.fn()
  512. vm = new Vue({
  513. el,
  514. methods: { foo: spy, bar: spy2 },
  515. data: {
  516. ok: true
  517. },
  518. render(h) {
  519. return this.ok
  520. ? h('input', { on: { click: this.foo } })
  521. : h('input', { on: { input: this.bar } })
  522. }
  523. })
  524. triggerEvent(vm.$el, 'click')
  525. expect(spy.mock.calls.length).toBe(1)
  526. expect(spy2.mock.calls.length).toBe(0)
  527. vm.ok = false
  528. waitForUpdate(() => {
  529. triggerEvent(vm.$el, 'click')
  530. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  531. triggerEvent(vm.$el, 'input')
  532. expect(spy2.mock.calls.length).toBe(1)
  533. }).then(done)
  534. })
  535. it('remove capturing listener', done => {
  536. const spy2 = vi.fn()
  537. vm = new Vue({
  538. el,
  539. methods: {
  540. foo: spy,
  541. bar: spy2,
  542. stopped(ev) {
  543. ev.stopPropagation()
  544. }
  545. },
  546. data: {
  547. ok: true
  548. },
  549. render(h) {
  550. return this.ok
  551. ? h('div', { on: { '!click': this.foo } }, [
  552. h('div', { on: { click: this.stopped } })
  553. ])
  554. : h('div', { on: { mouseOver: this.bar } }, [h('div')])
  555. }
  556. })
  557. triggerEvent(vm.$el.firstChild, 'click')
  558. expect(spy.mock.calls.length).toBe(1)
  559. expect(spy2.mock.calls.length).toBe(0)
  560. vm.ok = false
  561. waitForUpdate(() => {
  562. triggerEvent(vm.$el.firstChild, 'click')
  563. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  564. triggerEvent(vm.$el, 'mouseOver')
  565. expect(spy2.mock.calls.length).toBe(1)
  566. }).then(done)
  567. })
  568. it('remove once listener', done => {
  569. const spy2 = vi.fn()
  570. vm = new Vue({
  571. el,
  572. methods: { foo: spy, bar: spy2 },
  573. data: {
  574. ok: true
  575. },
  576. render(h) {
  577. return this.ok
  578. ? h('input', { on: { '~click': this.foo } })
  579. : h('input', { on: { input: this.bar } })
  580. }
  581. })
  582. triggerEvent(vm.$el, 'click')
  583. expect(spy.mock.calls.length).toBe(1)
  584. triggerEvent(vm.$el, 'click')
  585. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  586. expect(spy2.mock.calls.length).toBe(0)
  587. vm.ok = false
  588. waitForUpdate(() => {
  589. triggerEvent(vm.$el, 'click')
  590. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  591. triggerEvent(vm.$el, 'input')
  592. expect(spy2.mock.calls.length).toBe(1)
  593. }).then(done)
  594. })
  595. it('remove capturing and once listener', done => {
  596. const spy2 = vi.fn()
  597. vm = new Vue({
  598. el,
  599. methods: {
  600. foo: spy,
  601. bar: spy2,
  602. stopped(ev) {
  603. ev.stopPropagation()
  604. }
  605. },
  606. data: {
  607. ok: true
  608. },
  609. render(h) {
  610. return this.ok
  611. ? h('div', { on: { '~!click': this.foo } }, [
  612. h('div', { on: { click: this.stopped } })
  613. ])
  614. : h('div', { on: { mouseOver: this.bar } }, [h('div')])
  615. }
  616. })
  617. triggerEvent(vm.$el.firstChild, 'click')
  618. expect(spy.mock.calls.length).toBe(1)
  619. triggerEvent(vm.$el.firstChild, 'click')
  620. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  621. expect(spy2.mock.calls.length).toBe(0)
  622. vm.ok = false
  623. waitForUpdate(() => {
  624. triggerEvent(vm.$el.firstChild, 'click')
  625. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  626. triggerEvent(vm.$el, 'mouseOver')
  627. expect(spy2.mock.calls.length).toBe(1)
  628. }).then(done)
  629. })
  630. it('remove listener on child component', done => {
  631. const spy2 = vi.fn()
  632. vm = new Vue({
  633. el,
  634. methods: { foo: spy, bar: spy2 },
  635. data: {
  636. ok: true
  637. },
  638. components: {
  639. test: {
  640. template: '<div></div>'
  641. }
  642. },
  643. render(h) {
  644. return this.ok
  645. ? h('test', { on: { foo: this.foo } })
  646. : h('test', { on: { bar: this.bar } })
  647. }
  648. })
  649. vm.$children[0].$emit('foo')
  650. expect(spy.mock.calls.length).toBe(1)
  651. expect(spy2.mock.calls.length).toBe(0)
  652. vm.ok = false
  653. waitForUpdate(() => {
  654. vm.$children[0].$emit('foo')
  655. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  656. vm.$children[0].$emit('bar')
  657. expect(spy2.mock.calls.length).toBe(1)
  658. }).then(done)
  659. })
  660. it('warn missing handlers', () => {
  661. vm = new Vue({
  662. el,
  663. data: { none: null },
  664. template: `<div @click="none"></div>`
  665. })
  666. expect(`Invalid handler for event "click": got null`).toHaveBeenWarned()
  667. expect(() => {
  668. triggerEvent(vm.$el, 'click')
  669. }).not.toThrow()
  670. })
  671. // Github Issue #5046
  672. it('should support keyboard modifier for direction keys', () => {
  673. const spyLeft = vi.fn()
  674. const spyRight = vi.fn()
  675. const spyUp = vi.fn()
  676. const spyDown = vi.fn()
  677. vm = new Vue({
  678. el,
  679. template: `
  680. <div>
  681. <input ref="left" @keydown.left="foo"></input>
  682. <input ref="right" @keydown.right="foo1"></input>
  683. <input ref="up" @keydown.up="foo2"></input>
  684. <input ref="down" @keydown.down="foo3"></input>
  685. </div>
  686. `,
  687. methods: {
  688. foo: spyLeft,
  689. foo1: spyRight,
  690. foo2: spyUp,
  691. foo3: spyDown
  692. }
  693. })
  694. triggerEvent(vm.$refs.left, 'keydown', e => {
  695. e.keyCode = 37
  696. })
  697. triggerEvent(vm.$refs.left, 'keydown', e => {
  698. e.keyCode = 39
  699. })
  700. triggerEvent(vm.$refs.right, 'keydown', e => {
  701. e.keyCode = 39
  702. })
  703. triggerEvent(vm.$refs.right, 'keydown', e => {
  704. e.keyCode = 38
  705. })
  706. triggerEvent(vm.$refs.up, 'keydown', e => {
  707. e.keyCode = 38
  708. })
  709. triggerEvent(vm.$refs.up, 'keydown', e => {
  710. e.keyCode = 37
  711. })
  712. triggerEvent(vm.$refs.down, 'keydown', e => {
  713. e.keyCode = 40
  714. })
  715. triggerEvent(vm.$refs.down, 'keydown', e => {
  716. e.keyCode = 39
  717. })
  718. expect(spyLeft.mock.calls.length).toBe(1)
  719. expect(spyRight.mock.calls.length).toBe(1)
  720. expect(spyUp.mock.calls.length).toBe(1)
  721. expect(spyDown.mock.calls.length).toBe(1)
  722. })
  723. // This test case should only run when the test browser supports passive.
  724. if (supportsPassive) {
  725. it('should support passive', () => {
  726. vm = new Vue({
  727. el,
  728. template: `
  729. <div>
  730. <input type="checkbox" ref="normal" @click="foo"/>
  731. <input type="checkbox" ref="passive" @click.passive="foo"/>
  732. <input type="checkbox" ref="exclusive" @click.prevent.passive/>
  733. </div>
  734. `,
  735. methods: {
  736. foo(e) {
  737. e.preventDefault()
  738. }
  739. }
  740. })
  741. vm.$refs.normal.checked = false
  742. vm.$refs.passive.checked = false
  743. vm.$refs.exclusive.checked = false
  744. vm.$refs.normal.click()
  745. vm.$refs.passive.click()
  746. vm.$refs.exclusive.click()
  747. expect(vm.$refs.normal.checked).toBe(false)
  748. expect(vm.$refs.passive.checked).toBe(true)
  749. expect(vm.$refs.exclusive.checked).toBe(true)
  750. expect(
  751. "passive and prevent can't be used together. Passive handler can't prevent default event."
  752. ).toHaveBeenWarned()
  753. })
  754. }
  755. // GitHub Issues #5146
  756. it('should only prevent when match keycode', () => {
  757. let prevented = false
  758. vm = new Vue({
  759. el,
  760. template: `
  761. <input ref="input" @keydown.enter.prevent="foo">
  762. `,
  763. methods: {
  764. foo($event) {
  765. prevented = $event.defaultPrevented
  766. }
  767. }
  768. })
  769. triggerEvent(vm.$refs.input, 'keydown', e => {
  770. e.keyCode = 32
  771. })
  772. expect(prevented).toBe(false)
  773. triggerEvent(vm.$refs.input, 'keydown', e => {
  774. e.keyCode = 13
  775. })
  776. expect(prevented).toBe(true)
  777. })
  778. it('should transform click.right to contextmenu', () => {
  779. const spy = vi.fn()
  780. const vm = new Vue({
  781. template: `<div @click.right="foo"></div>`,
  782. methods: { foo: spy }
  783. }).$mount()
  784. triggerEvent(vm.$el, 'contextmenu')
  785. expect(spy).toHaveBeenCalled()
  786. })
  787. it('should transform click.middle to mouseup', () => {
  788. const spy = vi.fn()
  789. vm = new Vue({
  790. el,
  791. template: `<div @click.middle="foo"></div>`,
  792. methods: { foo: spy }
  793. })
  794. triggerEvent(vm.$el, 'mouseup', e => {
  795. e.button = 0
  796. })
  797. expect(spy).not.toHaveBeenCalled()
  798. triggerEvent(vm.$el, 'mouseup', e => {
  799. e.button = 1
  800. })
  801. expect(spy).toHaveBeenCalled()
  802. })
  803. it('object syntax (no argument)', () => {
  804. const click = vi.fn()
  805. const mouseup = vi.fn()
  806. vm = new Vue({
  807. el,
  808. template: `<button v-on="listeners">foo</button>`,
  809. created() {
  810. this.listeners = {
  811. click,
  812. mouseup
  813. }
  814. }
  815. })
  816. triggerEvent(vm.$el, 'click')
  817. expect(click.mock.calls.length).toBe(1)
  818. expect(mouseup.mock.calls.length).toBe(0)
  819. triggerEvent(vm.$el, 'mouseup')
  820. expect(click.mock.calls.length).toBe(1)
  821. expect(mouseup.mock.calls.length).toBe(1)
  822. })
  823. it('object syntax (no argument, mixed with normal listeners)', () => {
  824. const click1 = vi.fn()
  825. const click2 = vi.fn()
  826. const mouseup = vi.fn()
  827. vm = new Vue({
  828. el,
  829. template: `<button v-on="listeners" @click="click2">foo</button>`,
  830. created() {
  831. this.listeners = {
  832. click: click1,
  833. mouseup
  834. }
  835. },
  836. methods: {
  837. click2
  838. }
  839. })
  840. triggerEvent(vm.$el, 'click')
  841. expect(click1.mock.calls.length).toBe(1)
  842. expect(click2.mock.calls.length).toBe(1)
  843. expect(mouseup.mock.calls.length).toBe(0)
  844. triggerEvent(vm.$el, 'mouseup')
  845. expect(click1.mock.calls.length).toBe(1)
  846. expect(click2.mock.calls.length).toBe(1)
  847. expect(mouseup.mock.calls.length).toBe(1)
  848. })
  849. it('object syntax (usage in HOC, mixed with native listeners)', () => {
  850. const click = vi.fn()
  851. const mouseup = vi.fn()
  852. const mousedown = vi.fn()
  853. vm = new Vue({
  854. el,
  855. template: `
  856. <foo-button
  857. @click="click"
  858. @mousedown="mousedown"
  859. @mouseup.native="mouseup">
  860. </foo-button>
  861. `,
  862. methods: {
  863. click,
  864. mouseup,
  865. mousedown
  866. },
  867. components: {
  868. fooButton: {
  869. template: `
  870. <button v-on="$listeners"></button>
  871. `
  872. }
  873. }
  874. })
  875. triggerEvent(vm.$el, 'click')
  876. expect(click.mock.calls.length).toBe(1)
  877. expect(mouseup.mock.calls.length).toBe(0)
  878. expect(mousedown.mock.calls.length).toBe(0)
  879. triggerEvent(vm.$el, 'mouseup')
  880. expect(click.mock.calls.length).toBe(1)
  881. expect(mouseup.mock.calls.length).toBe(1)
  882. expect(mousedown.mock.calls.length).toBe(0)
  883. triggerEvent(vm.$el, 'mousedown')
  884. expect(click.mock.calls.length).toBe(1)
  885. expect(mouseup.mock.calls.length).toBe(1)
  886. expect(mousedown.mock.calls.length).toBe(1)
  887. })
  888. // #6805 (v-on="object" bind order problem)
  889. it('object syntax (no argument): should fire after high-priority listeners', done => {
  890. const MyCheckbox = {
  891. template: '<input type="checkbox" v-model="model" v-on="$listeners">',
  892. props: {
  893. value: false
  894. },
  895. computed: {
  896. model: {
  897. get() {
  898. return this.value
  899. },
  900. set(val) {
  901. this.$emit('input', val)
  902. }
  903. }
  904. }
  905. }
  906. vm = new Vue({
  907. el,
  908. template: `
  909. <div>
  910. <my-checkbox v-model="check" @change="change"></my-checkbox>
  911. </div>
  912. `,
  913. components: { MyCheckbox },
  914. data: {
  915. check: false
  916. },
  917. methods: {
  918. change() {
  919. expect(this.check).toBe(true)
  920. done()
  921. }
  922. }
  923. })
  924. vm.$el.querySelector('input').click()
  925. })
  926. it('warn object syntax with modifier', () => {
  927. new Vue({
  928. template: `<button v-on.self="{}"></button>`
  929. }).$mount()
  930. expect(
  931. `v-on without argument does not support modifiers`
  932. ).toHaveBeenWarned()
  933. })
  934. it('warn object syntax with non-object value', () => {
  935. new Vue({
  936. template: `<button v-on="123"></button>`
  937. }).$mount()
  938. expect(`v-on without argument expects an Object value`).toHaveBeenWarned()
  939. })
  940. it('should correctly remove once listener', done => {
  941. const vm = new Vue({
  942. template: `
  943. <div>
  944. <span v-if="ok" @click.once="foo">
  945. a
  946. </span>
  947. <span v-else a="a">
  948. b
  949. </span>
  950. </div>
  951. `,
  952. data: {
  953. ok: true
  954. },
  955. methods: {
  956. foo: spy
  957. }
  958. }).$mount()
  959. vm.ok = false
  960. waitForUpdate(() => {
  961. triggerEvent(vm.$el.childNodes[0], 'click')
  962. expect(spy.mock.calls.length).toBe(0)
  963. }).then(done)
  964. })
  965. // #7628
  966. it('handler should return the return value of inline function invocation', () => {
  967. let value
  968. new Vue({
  969. template: `<test @foo="bar()"></test>`,
  970. methods: {
  971. bar() {
  972. return 1
  973. }
  974. },
  975. components: {
  976. test: {
  977. created() {
  978. value = this.$listeners.foo()
  979. },
  980. render(h) {
  981. return h('div')
  982. }
  983. }
  984. }
  985. }).$mount()
  986. expect(value).toBe(1)
  987. })
  988. it('should not execute callback if modifiers are present', () => {
  989. vm = new Vue({
  990. el,
  991. template: '<input @keyup.?="foo">',
  992. methods: { foo: spy }
  993. })
  994. // simulating autocomplete event (Event object with type keyup but without keyCode)
  995. triggerEvent(vm.$el, 'keyup')
  996. expect(spy.mock.calls.length).toBe(0)
  997. })
  998. describe('dynamic arguments', () => {
  999. it('basic', done => {
  1000. const spy = vi.fn()
  1001. const vm = new Vue({
  1002. template: `<div v-on:[key]="spy"></div>`,
  1003. data: {
  1004. key: 'click'
  1005. },
  1006. methods: {
  1007. spy
  1008. }
  1009. }).$mount()
  1010. triggerEvent(vm.$el, 'click')
  1011. expect(spy.mock.calls.length).toBe(1)
  1012. vm.key = 'mouseup'
  1013. waitForUpdate(() => {
  1014. triggerEvent(vm.$el, 'click')
  1015. expect(spy.mock.calls.length).toBe(1)
  1016. triggerEvent(vm.$el, 'mouseup')
  1017. expect(spy.mock.calls.length).toBe(2)
  1018. // explicit null value
  1019. vm.key = null
  1020. })
  1021. .then(() => {
  1022. triggerEvent(vm.$el, 'click')
  1023. expect(spy.mock.calls.length).toBe(2)
  1024. triggerEvent(vm.$el, 'mouseup')
  1025. expect(spy.mock.calls.length).toBe(2)
  1026. })
  1027. .then(done)
  1028. })
  1029. it('shorthand', done => {
  1030. const spy = vi.fn()
  1031. const vm = new Vue({
  1032. template: `<div @[key]="spy"></div>`,
  1033. data: {
  1034. key: 'click'
  1035. },
  1036. methods: {
  1037. spy
  1038. }
  1039. }).$mount()
  1040. triggerEvent(vm.$el, 'click')
  1041. expect(spy.mock.calls.length).toBe(1)
  1042. vm.key = 'mouseup'
  1043. waitForUpdate(() => {
  1044. triggerEvent(vm.$el, 'click')
  1045. expect(spy.mock.calls.length).toBe(1)
  1046. triggerEvent(vm.$el, 'mouseup')
  1047. expect(spy.mock.calls.length).toBe(2)
  1048. }).then(done)
  1049. })
  1050. it('with .middle modifier', () => {
  1051. const spy = vi.fn()
  1052. const vm = new Vue({
  1053. template: `<div @[key].middle="spy"></div>`,
  1054. data: {
  1055. key: 'click'
  1056. },
  1057. methods: {
  1058. spy
  1059. }
  1060. }).$mount()
  1061. triggerEvent(vm.$el, 'mouseup', e => {
  1062. e.button = 0
  1063. })
  1064. expect(spy).not.toHaveBeenCalled()
  1065. triggerEvent(vm.$el, 'mouseup', e => {
  1066. e.button = 1
  1067. })
  1068. expect(spy).toHaveBeenCalled()
  1069. })
  1070. it('with .right modifier', () => {
  1071. const spy = vi.fn()
  1072. const vm = new Vue({
  1073. template: `<div @[key].right="spy"></div>`,
  1074. data: {
  1075. key: 'click'
  1076. },
  1077. methods: {
  1078. spy
  1079. }
  1080. }).$mount()
  1081. triggerEvent(vm.$el, 'contextmenu')
  1082. expect(spy).toHaveBeenCalled()
  1083. })
  1084. it('with .capture modifier', () => {
  1085. const callOrder: any[] = []
  1086. const vm = new Vue({
  1087. template: `
  1088. <div @[key].capture="foo">
  1089. <div @[key]="bar"></div>
  1090. </div>
  1091. `,
  1092. data: {
  1093. key: 'click'
  1094. },
  1095. methods: {
  1096. foo() {
  1097. callOrder.push(1)
  1098. },
  1099. bar() {
  1100. callOrder.push(2)
  1101. }
  1102. }
  1103. }).$mount()
  1104. triggerEvent(vm.$el.firstChild, 'click')
  1105. expect(callOrder.toString()).toBe('1,2')
  1106. })
  1107. it('with .once modifier', () => {
  1108. const vm = new Vue({
  1109. template: `<div @[key].once="foo"></div>`,
  1110. data: { key: 'click' },
  1111. methods: { foo: spy }
  1112. }).$mount()
  1113. triggerEvent(vm.$el, 'click')
  1114. expect(spy.mock.calls.length).toBe(1)
  1115. triggerEvent(vm.$el, 'click')
  1116. expect(spy.mock.calls.length).toBe(1) // should no longer trigger
  1117. })
  1118. })
  1119. })