on.spec.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. import Vue from 'vue'
  2. import { supportsPassive } from 'core/util/env'
  3. describe('Directive v-on', () => {
  4. let vm, spy, el
  5. beforeEach(() => {
  6. vm = null
  7. spy = jasmine.createSpy()
  8. el = document.createElement('div')
  9. document.body.appendChild(el)
  10. })
  11. afterEach(() => {
  12. if (vm) {
  13. document.body.removeChild(vm.$el)
  14. }
  15. })
  16. it('should bind event to a method', () => {
  17. vm = new Vue({
  18. el,
  19. template: '<div v-on:click="foo"></div>',
  20. methods: { foo: spy }
  21. })
  22. triggerEvent(vm.$el, 'click')
  23. expect(spy.calls.count()).toBe(1)
  24. const args = spy.calls.allArgs()
  25. const event = args[0] && args[0][0] || {}
  26. expect(event.type).toBe('click')
  27. })
  28. it('should bind event to a inline statement', () => {
  29. vm = new Vue({
  30. el,
  31. template: '<div v-on:click="foo(1,2,3,$event)"></div>',
  32. methods: { foo: spy }
  33. })
  34. triggerEvent(vm.$el, 'click')
  35. expect(spy.calls.count()).toBe(1)
  36. const args = spy.calls.allArgs()
  37. const firstArgs = args[0]
  38. expect(firstArgs.length).toBe(4)
  39. expect(firstArgs[0]).toBe(1)
  40. expect(firstArgs[1]).toBe(2)
  41. expect(firstArgs[2]).toBe(3)
  42. expect(firstArgs[3].type).toBe('click')
  43. })
  44. it('should support inline function expression', () => {
  45. const spy = jasmine.createSpy()
  46. vm = new Vue({
  47. el,
  48. template: `<div class="test" @click="function (e) { log(e.target.className) }"></div>`,
  49. methods: {
  50. log: spy
  51. }
  52. }).$mount()
  53. triggerEvent(vm.$el, 'click')
  54. expect(spy).toHaveBeenCalledWith('test')
  55. })
  56. it('should support shorthand', () => {
  57. vm = new Vue({
  58. el,
  59. template: '<a href="#test" @click.prevent="foo"></a>',
  60. methods: { foo: spy }
  61. })
  62. triggerEvent(vm.$el, 'click')
  63. expect(spy.calls.count()).toBe(1)
  64. })
  65. it('should support stop propagation', () => {
  66. vm = new Vue({
  67. el,
  68. template: `
  69. <div @click.stop="foo"></div>
  70. `,
  71. methods: { foo: spy }
  72. })
  73. const hash = window.location.hash
  74. triggerEvent(vm.$el, 'click')
  75. expect(window.location.hash).toBe(hash)
  76. })
  77. it('should support prevent default', () => {
  78. vm = new Vue({
  79. el,
  80. template: `
  81. <input type="checkbox" ref="input" @click.prevent="foo">
  82. `,
  83. methods: {
  84. foo ($event) {
  85. spy($event.defaultPrevented)
  86. }
  87. }
  88. })
  89. vm.$refs.input.checked = false
  90. triggerEvent(vm.$refs.input, 'click')
  91. expect(spy).toHaveBeenCalledWith(true)
  92. })
  93. it('should support capture', () => {
  94. const callOrder = []
  95. vm = new Vue({
  96. el,
  97. template: `
  98. <div @click.capture="foo">
  99. <div @click="bar"></div>
  100. </div>
  101. `,
  102. methods: {
  103. foo () { callOrder.push(1) },
  104. bar () { callOrder.push(2) }
  105. }
  106. })
  107. triggerEvent(vm.$el.firstChild, 'click')
  108. expect(callOrder.toString()).toBe('1,2')
  109. })
  110. it('should support once', () => {
  111. vm = new Vue({
  112. el,
  113. template: `
  114. <div @click.once="foo">
  115. </div>
  116. `,
  117. methods: { foo: spy }
  118. })
  119. triggerEvent(vm.$el, 'click')
  120. expect(spy.calls.count()).toBe(1)
  121. triggerEvent(vm.$el, 'click')
  122. expect(spy.calls.count()).toBe(1) // should no longer trigger
  123. })
  124. // #4655
  125. it('should handle .once on multiple elements properly', () => {
  126. vm = new Vue({
  127. el,
  128. template: `
  129. <div>
  130. <button ref="one" @click.once="foo">one</button>
  131. <button ref="two" @click.once="foo">two</button>
  132. </div>
  133. `,
  134. methods: { foo: spy }
  135. })
  136. triggerEvent(vm.$refs.one, 'click')
  137. expect(spy.calls.count()).toBe(1)
  138. triggerEvent(vm.$refs.one, 'click')
  139. expect(spy.calls.count()).toBe(1)
  140. triggerEvent(vm.$refs.two, 'click')
  141. expect(spy.calls.count()).toBe(2)
  142. triggerEvent(vm.$refs.one, 'click')
  143. triggerEvent(vm.$refs.two, 'click')
  144. expect(spy.calls.count()).toBe(2)
  145. })
  146. it('should support capture and once', () => {
  147. const callOrder = []
  148. vm = new Vue({
  149. el,
  150. template: `
  151. <div @click.capture.once="foo">
  152. <div @click="bar"></div>
  153. </div>
  154. `,
  155. methods: {
  156. foo () { callOrder.push(1) },
  157. bar () { callOrder.push(2) }
  158. }
  159. })
  160. triggerEvent(vm.$el.firstChild, 'click')
  161. expect(callOrder.toString()).toBe('1,2')
  162. triggerEvent(vm.$el.firstChild, 'click')
  163. expect(callOrder.toString()).toBe('1,2,2')
  164. })
  165. // #4846
  166. it('should support once and other modifiers', () => {
  167. vm = new Vue({
  168. el,
  169. template: `<div @click.once.self="foo"><span/></div>`,
  170. methods: { foo: spy }
  171. })
  172. triggerEvent(vm.$el.firstChild, 'click')
  173. expect(spy).not.toHaveBeenCalled()
  174. triggerEvent(vm.$el, 'click')
  175. expect(spy).toHaveBeenCalled()
  176. triggerEvent(vm.$el, 'click')
  177. expect(spy.calls.count()).toBe(1)
  178. })
  179. it('should support keyCode', () => {
  180. vm = new Vue({
  181. el,
  182. template: `<input @keyup.enter="foo">`,
  183. methods: { foo: spy }
  184. })
  185. triggerEvent(vm.$el, 'keyup', e => {
  186. e.keyCode = 13
  187. })
  188. expect(spy).toHaveBeenCalled()
  189. })
  190. it('should support number keyCode', () => {
  191. vm = new Vue({
  192. el,
  193. template: `<input @keyup.13="foo">`,
  194. methods: { foo: spy }
  195. })
  196. triggerEvent(vm.$el, 'keyup', e => {
  197. e.keyCode = 13
  198. })
  199. expect(spy).toHaveBeenCalled()
  200. })
  201. it('should support mouse modifier', () => {
  202. const left = 0
  203. const middle = 1
  204. const right = 2
  205. const spyLeft = jasmine.createSpy()
  206. const spyMiddle = jasmine.createSpy()
  207. const spyRight = jasmine.createSpy()
  208. vm = new Vue({
  209. el,
  210. template: `
  211. <div>
  212. <div ref="left" @mousedown.left="foo">left</div>
  213. <div ref="right" @mousedown.right="foo1">right</div>
  214. <div ref="middle" @mousedown.middle="foo2">right</div>
  215. </div>
  216. `,
  217. methods: {
  218. foo: spyLeft,
  219. foo1: spyRight,
  220. foo2: spyMiddle
  221. }
  222. })
  223. triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = right })
  224. triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = middle })
  225. expect(spyLeft).not.toHaveBeenCalled()
  226. triggerEvent(vm.$refs.left, 'mousedown', e => { e.button = left })
  227. expect(spyLeft).toHaveBeenCalled()
  228. triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = left })
  229. triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = middle })
  230. expect(spyRight).not.toHaveBeenCalled()
  231. triggerEvent(vm.$refs.right, 'mousedown', e => { e.button = right })
  232. expect(spyRight).toHaveBeenCalled()
  233. triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = left })
  234. triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = right })
  235. expect(spyMiddle).not.toHaveBeenCalled()
  236. triggerEvent(vm.$refs.middle, 'mousedown', e => { e.button = middle })
  237. expect(spyMiddle).toHaveBeenCalled()
  238. })
  239. it('should support custom keyCode', () => {
  240. Vue.config.keyCodes.test = 1
  241. vm = new Vue({
  242. el,
  243. template: `<input @keyup.test="foo">`,
  244. methods: { foo: spy }
  245. })
  246. triggerEvent(vm.$el, 'keyup', e => {
  247. e.keyCode = 1
  248. })
  249. expect(spy).toHaveBeenCalled()
  250. Vue.config.keyCodes = Object.create(null)
  251. })
  252. it('should override build-in keyCode', () => {
  253. Vue.config.keyCodes.up = [1, 87]
  254. vm = new Vue({
  255. el,
  256. template: `<input @keyup.up="foo" @keyup.down="foo">`,
  257. methods: { foo: spy }
  258. })
  259. triggerEvent(vm.$el, 'keyup', e => {
  260. e.keyCode = 87
  261. })
  262. expect(spy).toHaveBeenCalled()
  263. triggerEvent(vm.$el, 'keyup', e => {
  264. e.keyCode = 1
  265. })
  266. expect(spy).toHaveBeenCalledTimes(2)
  267. // should not affect build-in down keycode
  268. triggerEvent(vm.$el, 'keyup', e => {
  269. e.keyCode = 40
  270. })
  271. expect(spy).toHaveBeenCalledTimes(3)
  272. Vue.config.keyCodes = Object.create(null)
  273. })
  274. it('should bind to a child component', () => {
  275. Vue.component('bar', {
  276. template: '<span>Hello</span>'
  277. })
  278. vm = new Vue({
  279. el,
  280. template: '<bar @custom="foo"></bar>',
  281. methods: { foo: spy }
  282. })
  283. vm.$children[0].$emit('custom', 'foo', 'bar')
  284. expect(spy).toHaveBeenCalledWith('foo', 'bar')
  285. })
  286. it('should be able to bind native events for a child component', () => {
  287. Vue.component('bar', {
  288. template: '<span>Hello</span>'
  289. })
  290. vm = new Vue({
  291. el,
  292. template: '<bar @click.native="foo"></bar>',
  293. methods: { foo: spy }
  294. })
  295. vm.$children[0].$emit('click')
  296. expect(spy).not.toHaveBeenCalled()
  297. triggerEvent(vm.$children[0].$el, 'click')
  298. expect(spy).toHaveBeenCalled()
  299. })
  300. it('.once modifier should work with child components', () => {
  301. Vue.component('bar', {
  302. template: '<span>Hello</span>'
  303. })
  304. vm = new Vue({
  305. el,
  306. template: '<bar @custom.once="foo"></bar>',
  307. methods: { foo: spy }
  308. })
  309. vm.$children[0].$emit('custom')
  310. expect(spy.calls.count()).toBe(1)
  311. vm.$children[0].$emit('custom')
  312. expect(spy.calls.count()).toBe(1) // should not be called again
  313. })
  314. it('remove listener', done => {
  315. const spy2 = jasmine.createSpy('remove listener')
  316. vm = new Vue({
  317. el,
  318. methods: { foo: spy, bar: spy2 },
  319. data: {
  320. ok: true
  321. },
  322. render (h) {
  323. return this.ok
  324. ? h('input', { on: { click: this.foo }})
  325. : h('input', { on: { input: this.bar }})
  326. }
  327. })
  328. triggerEvent(vm.$el, 'click')
  329. expect(spy.calls.count()).toBe(1)
  330. expect(spy2.calls.count()).toBe(0)
  331. vm.ok = false
  332. waitForUpdate(() => {
  333. triggerEvent(vm.$el, 'click')
  334. expect(spy.calls.count()).toBe(1) // should no longer trigger
  335. triggerEvent(vm.$el, 'input')
  336. expect(spy2.calls.count()).toBe(1)
  337. }).then(done)
  338. })
  339. it('remove capturing listener', done => {
  340. const spy2 = jasmine.createSpy('remove listener')
  341. vm = new Vue({
  342. el,
  343. methods: { foo: spy, bar: spy2, stopped (ev) { ev.stopPropagation() } },
  344. data: {
  345. ok: true
  346. },
  347. render (h) {
  348. return this.ok
  349. ? h('div', { on: { '!click': this.foo }}, [h('div', { on: { click: this.stopped }})])
  350. : h('div', { on: { mouseOver: this.bar }}, [h('div')])
  351. }
  352. })
  353. triggerEvent(vm.$el.firstChild, 'click')
  354. expect(spy.calls.count()).toBe(1)
  355. expect(spy2.calls.count()).toBe(0)
  356. vm.ok = false
  357. waitForUpdate(() => {
  358. triggerEvent(vm.$el.firstChild, 'click')
  359. expect(spy.calls.count()).toBe(1) // should no longer trigger
  360. triggerEvent(vm.$el, 'mouseOver')
  361. expect(spy2.calls.count()).toBe(1)
  362. }).then(done)
  363. })
  364. it('remove once listener', done => {
  365. const spy2 = jasmine.createSpy('remove listener')
  366. vm = new Vue({
  367. el,
  368. methods: { foo: spy, bar: spy2 },
  369. data: {
  370. ok: true
  371. },
  372. render (h) {
  373. return this.ok
  374. ? h('input', { on: { '~click': this.foo }})
  375. : h('input', { on: { input: this.bar }})
  376. }
  377. })
  378. triggerEvent(vm.$el, 'click')
  379. expect(spy.calls.count()).toBe(1)
  380. triggerEvent(vm.$el, 'click')
  381. expect(spy.calls.count()).toBe(1) // should no longer trigger
  382. expect(spy2.calls.count()).toBe(0)
  383. vm.ok = false
  384. waitForUpdate(() => {
  385. triggerEvent(vm.$el, 'click')
  386. expect(spy.calls.count()).toBe(1) // should no longer trigger
  387. triggerEvent(vm.$el, 'input')
  388. expect(spy2.calls.count()).toBe(1)
  389. }).then(done)
  390. })
  391. it('remove capturing and once listener', done => {
  392. const spy2 = jasmine.createSpy('remove listener')
  393. vm = new Vue({
  394. el,
  395. methods: { foo: spy, bar: spy2, stopped (ev) { ev.stopPropagation() } },
  396. data: {
  397. ok: true
  398. },
  399. render (h) {
  400. return this.ok
  401. ? h('div', { on: { '~!click': this.foo }}, [h('div', { on: { click: this.stopped }})])
  402. : h('div', { on: { mouseOver: this.bar }}, [h('div')])
  403. }
  404. })
  405. triggerEvent(vm.$el.firstChild, 'click')
  406. expect(spy.calls.count()).toBe(1)
  407. triggerEvent(vm.$el.firstChild, 'click')
  408. expect(spy.calls.count()).toBe(1) // should no longer trigger
  409. expect(spy2.calls.count()).toBe(0)
  410. vm.ok = false
  411. waitForUpdate(() => {
  412. triggerEvent(vm.$el.firstChild, 'click')
  413. expect(spy.calls.count()).toBe(1) // should no longer trigger
  414. triggerEvent(vm.$el, 'mouseOver')
  415. expect(spy2.calls.count()).toBe(1)
  416. }).then(done)
  417. })
  418. it('remove listener on child component', done => {
  419. const spy2 = jasmine.createSpy('remove listener')
  420. vm = new Vue({
  421. el,
  422. methods: { foo: spy, bar: spy2 },
  423. data: {
  424. ok: true
  425. },
  426. components: {
  427. test: {
  428. template: '<div></div>'
  429. }
  430. },
  431. render (h) {
  432. return this.ok
  433. ? h('test', { on: { foo: this.foo }})
  434. : h('test', { on: { bar: this.bar }})
  435. }
  436. })
  437. vm.$children[0].$emit('foo')
  438. expect(spy.calls.count()).toBe(1)
  439. expect(spy2.calls.count()).toBe(0)
  440. vm.ok = false
  441. waitForUpdate(() => {
  442. vm.$children[0].$emit('foo')
  443. expect(spy.calls.count()).toBe(1) // should no longer trigger
  444. vm.$children[0].$emit('bar')
  445. expect(spy2.calls.count()).toBe(1)
  446. }).then(done)
  447. })
  448. it('warn missing handlers', () => {
  449. vm = new Vue({
  450. el,
  451. data: { none: null },
  452. template: `<div @click="none"></div>`
  453. })
  454. expect(`Invalid handler for event "click": got null`).toHaveBeenWarned()
  455. expect(() => {
  456. triggerEvent(vm.$el, 'click')
  457. }).not.toThrow()
  458. })
  459. // Github Issue #5046
  460. it('should support keyboard modifier', () => {
  461. const spyLeft = jasmine.createSpy()
  462. const spyRight = jasmine.createSpy()
  463. const spyUp = jasmine.createSpy()
  464. const spyDown = jasmine.createSpy()
  465. vm = new Vue({
  466. el,
  467. template: `
  468. <div>
  469. <input ref="left" @keydown.left="foo"></input>
  470. <input ref="right" @keydown.right="foo1"></input>
  471. <input ref="up" @keydown.up="foo2"></input>
  472. <input ref="down" @keydown.down="foo3"></input>
  473. </div>
  474. `,
  475. methods: {
  476. foo: spyLeft,
  477. foo1: spyRight,
  478. foo2: spyUp,
  479. foo3: spyDown
  480. }
  481. })
  482. triggerEvent(vm.$refs.left, 'keydown', e => { e.keyCode = 37 })
  483. triggerEvent(vm.$refs.left, 'keydown', e => { e.keyCode = 39 })
  484. triggerEvent(vm.$refs.right, 'keydown', e => { e.keyCode = 39 })
  485. triggerEvent(vm.$refs.right, 'keydown', e => { e.keyCode = 38 })
  486. triggerEvent(vm.$refs.up, 'keydown', e => { e.keyCode = 38 })
  487. triggerEvent(vm.$refs.up, 'keydown', e => { e.keyCode = 37 })
  488. triggerEvent(vm.$refs.down, 'keydown', e => { e.keyCode = 40 })
  489. triggerEvent(vm.$refs.down, 'keydown', e => { e.keyCode = 39 })
  490. expect(spyLeft.calls.count()).toBe(1)
  491. expect(spyRight.calls.count()).toBe(1)
  492. expect(spyUp.calls.count()).toBe(1)
  493. expect(spyDown.calls.count()).toBe(1)
  494. })
  495. // This test case should only run when the test browser supports passive.
  496. if (supportsPassive) {
  497. it('should support passive', () => {
  498. vm = new Vue({
  499. el,
  500. template: `
  501. <div>
  502. <input type="checkbox" ref="normal" @click="foo"/>
  503. <input type="checkbox" ref="passive" @click.passive="foo"/>
  504. <input type="checkbox" ref="exclusive" @click.prevent.passive/>
  505. </div>
  506. `,
  507. methods: {
  508. foo (e) {
  509. e.preventDefault()
  510. }
  511. }
  512. })
  513. vm.$refs.normal.checked = false
  514. vm.$refs.passive.checked = false
  515. vm.$refs.exclusive.checked = false
  516. vm.$refs.normal.click()
  517. vm.$refs.passive.click()
  518. vm.$refs.exclusive.click()
  519. expect(vm.$refs.normal.checked).toBe(false)
  520. expect(vm.$refs.passive.checked).toBe(true)
  521. expect(vm.$refs.exclusive.checked).toBe(true)
  522. expect('passive and prevent can\'t be used together. Passive handler can\'t prevent default event.').toHaveBeenWarned()
  523. })
  524. }
  525. // Github Issues #5146
  526. it('should only prevent when match keycode', () => {
  527. let prevented = false
  528. vm = new Vue({
  529. el,
  530. template: `
  531. <input ref="input" @keydown.enter.prevent="foo">
  532. `,
  533. methods: {
  534. foo ($event) {
  535. prevented = $event.defaultPrevented
  536. }
  537. }
  538. })
  539. triggerEvent(vm.$refs.input, 'keydown', e => { e.keyCode = 32 })
  540. expect(prevented).toBe(false)
  541. triggerEvent(vm.$refs.input, 'keydown', e => { e.keyCode = 13 })
  542. expect(prevented).toBe(true)
  543. })
  544. it('should warn click.right', () => {
  545. new Vue({
  546. template: `<div @click.right="foo"></div>`,
  547. methods: { foo () {} }
  548. }).$mount()
  549. expect(`Use "contextmenu" instead`).toHaveBeenWarned()
  550. })
  551. })