on.spec.js 15 KB

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