patchEvents.spec.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import { patchProp } from '../src/patchProp'
  2. const timeout = () => new Promise(r => setTimeout(r))
  3. describe(`runtime-dom: events patching`, () => {
  4. it('should assign event handler', async () => {
  5. const el = document.createElement('div')
  6. const event = new Event('click')
  7. const fn = jest.fn()
  8. patchProp(el, 'onClick', null, fn)
  9. el.dispatchEvent(event)
  10. await timeout()
  11. el.dispatchEvent(event)
  12. await timeout()
  13. el.dispatchEvent(event)
  14. await timeout()
  15. expect(fn).toHaveBeenCalledTimes(3)
  16. })
  17. it('should update event handler', async () => {
  18. const el = document.createElement('div')
  19. const event = new Event('click')
  20. const prevFn = jest.fn()
  21. const nextFn = jest.fn()
  22. patchProp(el, 'onClick', null, prevFn)
  23. el.dispatchEvent(event)
  24. patchProp(el, 'onClick', prevFn, nextFn)
  25. await timeout()
  26. el.dispatchEvent(event)
  27. await timeout()
  28. el.dispatchEvent(event)
  29. await timeout()
  30. expect(prevFn).toHaveBeenCalledTimes(1)
  31. expect(nextFn).toHaveBeenCalledTimes(2)
  32. })
  33. it('should support multiple event handlers', async () => {
  34. const el = document.createElement('div')
  35. const event = new Event('click')
  36. const fn1 = jest.fn()
  37. const fn2 = jest.fn()
  38. patchProp(el, 'onClick', null, [fn1, fn2])
  39. el.dispatchEvent(event)
  40. await timeout()
  41. expect(fn1).toHaveBeenCalledTimes(1)
  42. expect(fn2).toHaveBeenCalledTimes(1)
  43. })
  44. it('should unassign event handler', async () => {
  45. const el = document.createElement('div')
  46. const event = new Event('click')
  47. const fn = jest.fn()
  48. patchProp(el, 'onClick', null, fn)
  49. patchProp(el, 'onClick', fn, null)
  50. el.dispatchEvent(event)
  51. await timeout()
  52. expect(fn).not.toHaveBeenCalled()
  53. })
  54. it('should support event option modifiers', async () => {
  55. const el = document.createElement('div')
  56. const event = new Event('click')
  57. const fn = jest.fn()
  58. patchProp(el, 'onClickOnceCapture', null, fn)
  59. el.dispatchEvent(event)
  60. await timeout()
  61. el.dispatchEvent(event)
  62. await timeout()
  63. expect(fn).toHaveBeenCalledTimes(1)
  64. })
  65. it('should unassign event handler with options', async () => {
  66. const el = document.createElement('div')
  67. const event = new Event('click')
  68. const fn = jest.fn()
  69. patchProp(el, 'onClickCapture', null, fn)
  70. el.dispatchEvent(event)
  71. await timeout()
  72. expect(fn).toHaveBeenCalledTimes(1)
  73. patchProp(el, 'onClickCapture', fn, null)
  74. el.dispatchEvent(event)
  75. await timeout()
  76. el.dispatchEvent(event)
  77. await timeout()
  78. expect(fn).toHaveBeenCalledTimes(1)
  79. })
  80. it('should support native onclick', async () => {
  81. const el = document.createElement('div')
  82. const event = new Event('click')
  83. // string should be set as attribute
  84. const fn = ((window as any).__globalSpy = jest.fn())
  85. patchProp(el, 'onclick', null, '__globalSpy(1)')
  86. el.dispatchEvent(event)
  87. await timeout()
  88. delete (window as any).__globalSpy
  89. expect(fn).toHaveBeenCalledWith(1)
  90. const fn2 = jest.fn()
  91. patchProp(el, 'onclick', '__globalSpy(1)', fn2)
  92. el.dispatchEvent(event)
  93. await timeout()
  94. expect(fn).toHaveBeenCalledTimes(1)
  95. expect(fn2).toHaveBeenCalledWith(event)
  96. })
  97. it('should support stopImmediatePropagation on multiple listeners', async () => {
  98. const el = document.createElement('div')
  99. const event = new Event('click')
  100. const fn1 = jest.fn((e: Event) => {
  101. e.stopImmediatePropagation()
  102. })
  103. const fn2 = jest.fn()
  104. patchProp(el, 'onClick', null, [fn1, fn2])
  105. el.dispatchEvent(event)
  106. await timeout()
  107. expect(fn1).toHaveBeenCalledTimes(1)
  108. expect(fn2).toHaveBeenCalledTimes(0)
  109. })
  110. // #1747
  111. it('should handle same computed handler function being bound on multiple targets', async () => {
  112. const el1 = document.createElement('div')
  113. const el2 = document.createElement('div')
  114. const event = new Event('click')
  115. const prevFn = jest.fn()
  116. const nextFn = jest.fn()
  117. patchProp(el1, 'onClick', null, prevFn)
  118. patchProp(el2, 'onClick', null, prevFn)
  119. el1.dispatchEvent(event)
  120. el2.dispatchEvent(event)
  121. await timeout()
  122. expect(prevFn).toHaveBeenCalledTimes(2)
  123. expect(nextFn).toHaveBeenCalledTimes(0)
  124. patchProp(el1, 'onClick', prevFn, nextFn)
  125. patchProp(el2, 'onClick', prevFn, nextFn)
  126. el1.dispatchEvent(event)
  127. el2.dispatchEvent(event)
  128. await timeout()
  129. expect(prevFn).toHaveBeenCalledTimes(2)
  130. expect(nextFn).toHaveBeenCalledTimes(2)
  131. el1.dispatchEvent(event)
  132. el2.dispatchEvent(event)
  133. await timeout()
  134. expect(prevFn).toHaveBeenCalledTimes(2)
  135. expect(nextFn).toHaveBeenCalledTimes(4)
  136. })
  137. // #2841
  138. test('should patch event correctly in web-components', async () => {
  139. class TestElement extends HTMLElement {
  140. constructor() {
  141. super()
  142. }
  143. }
  144. window.customElements.define('test-element', TestElement)
  145. const testElement = document.createElement('test-element', {
  146. is: 'test-element'
  147. })
  148. const fn1 = jest.fn()
  149. const fn2 = jest.fn()
  150. // in webComponents, @foo-bar will patch prop 'onFooBar'
  151. // and @foobar will patch prop 'onFoobar'
  152. patchProp(testElement, 'onFooBar', null, fn1)
  153. testElement.dispatchEvent(new CustomEvent('foo-bar'))
  154. expect(fn1).toHaveBeenCalledTimes(1)
  155. patchProp(testElement, 'onFoobar', null, fn2)
  156. testElement.dispatchEvent(new CustomEvent('foobar'))
  157. expect(fn2).toHaveBeenCalledTimes(1)
  158. })
  159. })