on.spec.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import Alpine from 'alpinejs'
  2. import { wait, fireEvent } from '@testing-library/dom'
  3. const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
  4. global.MutationObserver = class {
  5. observe() {}
  6. }
  7. test('data modified in event listener updates effected attribute bindings', async () => {
  8. document.body.innerHTML = `
  9. <div x-data="{ foo: 'bar' }">
  10. <button x-on:click="foo = 'baz'"></button>
  11. <span x-bind:foo="foo"></span>
  12. </div>
  13. `
  14. Alpine.start()
  15. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  16. document.querySelector('button').click()
  17. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  18. })
  19. test('nested data modified in event listener updates effected attribute bindings', async () => {
  20. document.body.innerHTML = `
  21. <div x-data="{ nested: { foo: 'bar' } }">
  22. <button x-on:click="nested.foo = 'baz'"></button>
  23. <span x-bind:foo="nested.foo"></span>
  24. </div>
  25. `
  26. Alpine.start()
  27. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  28. document.querySelector('button').click()
  29. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  30. })
  31. test('.stop modifier', async () => {
  32. document.body.innerHTML = `
  33. <div x-data="{ foo: 'bar' }">
  34. <button x-on:click="foo = 'baz'">
  35. <span></span>
  36. </button>
  37. </div>
  38. `
  39. Alpine.start()
  40. expect(document.querySelector('div').__x.$data.foo).toEqual('bar')
  41. document.querySelector('span').click()
  42. await wait(() => {
  43. expect(document.querySelector('div').__x.$data.foo).toEqual('baz')
  44. })
  45. })
  46. test('.prevent modifier', async () => {
  47. document.body.innerHTML = `
  48. <div x-data="{}">
  49. <input type="checkbox" x-on:click.prevent>
  50. </div>
  51. `
  52. Alpine.start()
  53. expect(document.querySelector('input').checked).toEqual(false)
  54. document.querySelector('input').click()
  55. expect(document.querySelector('input').checked).toEqual(false)
  56. })
  57. test('.window modifier', async () => {
  58. document.body.innerHTML = `
  59. <div x-data="{ foo: 'bar' }">
  60. <div x-on:click.window="foo = 'baz'"></div>
  61. <span x-bind:foo="foo"></span>
  62. </div>
  63. `
  64. Alpine.start()
  65. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  66. document.body.click()
  67. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  68. })
  69. test('.document modifier', async () => {
  70. document.body.innerHTML = `
  71. <div x-data="{ foo: 'bar' }">
  72. <div x-on:click.document="foo = 'baz'"></div>
  73. <span x-bind:foo="foo"></span>
  74. </div>
  75. `
  76. Alpine.start()
  77. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  78. document.body.click()
  79. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  80. })
  81. test('.once modifier', async () => {
  82. document.body.innerHTML = `
  83. <div x-data="{ count: 0 }">
  84. <button x-on:click.once="count = count+1"></button>
  85. <span x-bind:foo="count"
  86. </div>
  87. `
  88. Alpine.start()
  89. expect(document.querySelector('span').getAttribute('foo')).toEqual('0')
  90. document.querySelector('button').click()
  91. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('1') })
  92. document.querySelector('button').click()
  93. await timeout(25)
  94. expect(document.querySelector('span').getAttribute('foo')).toEqual('1')
  95. })
  96. test('keydown modifiers', async () => {
  97. document.body.innerHTML = `
  98. <div x-data="{ count: 0 }">
  99. <input type="text" x-on:keydown="count++" x-on:keydown.enter="count++" x-on:keydown.space="count++">
  100. <span x-text="count"></span>
  101. </div>
  102. `
  103. Alpine.start()
  104. expect(document.querySelector('span').innerText).toEqual(0)
  105. fireEvent.keyDown(document.querySelector('input'), { key: 'Enter' })
  106. await wait(() => { expect(document.querySelector('span').innerText).toEqual(2) })
  107. fireEvent.keyDown(document.querySelector('input'), { key: ' ' })
  108. await wait(() => { expect(document.querySelector('span').innerText).toEqual(4) })
  109. fireEvent.keyDown(document.querySelector('input'), { key: 'Spacebar' })
  110. await wait(() => { expect(document.querySelector('span').innerText).toEqual(6) })
  111. fireEvent.keyDown(document.querySelector('input'), { key: 'Escape' })
  112. await wait(() => { expect(document.querySelector('span').innerText).toEqual(7) })
  113. })
  114. test('keydown combo modifiers', async () => {
  115. document.body.innerHTML = `
  116. <div x-data="{ count: 0 }">
  117. <input type="text" x-on:keydown.cmd.enter="count++">
  118. <span x-text="count"></span>
  119. </div>
  120. `
  121. Alpine.start()
  122. expect(document.querySelector('span').innerText).toEqual(0)
  123. fireEvent.keyDown(document.querySelector('input'), { key: 'Enter' })
  124. await wait(() => { expect(document.querySelector('span').innerText).toEqual(0) })
  125. fireEvent.keyDown(document.querySelector('input'), { key: 'Enter', metaKey: true })
  126. await wait(() => { expect(document.querySelector('span').innerText).toEqual(1) })
  127. })
  128. test('click away', async () => {
  129. // Because jsDom doesn't support .offsetHeight and offsetWidth, we have to
  130. // make our own implementation using a specific class added to the class. Ugh.
  131. Object.defineProperties(window.HTMLElement.prototype, {
  132. offsetHeight: {
  133. get: function() { return this.classList.contains('hidden') ? 0 : 1 }
  134. },
  135. offsetWidth: {
  136. get: function() { return this.classList.contains('hidden') ? 0 : 1 }
  137. }
  138. });
  139. document.body.innerHTML = `
  140. <div id="outer">
  141. <div x-data="{ isOpen: true }">
  142. <button x-on:click="isOpen = true"></button>
  143. <ul x-bind:class="{ 'hidden': ! isOpen }" x-on:click.away="isOpen = false">
  144. <li>...</li>
  145. </ul>
  146. </div>
  147. </div>
  148. `
  149. Alpine.start()
  150. expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false)
  151. document.querySelector('li').click()
  152. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  153. document.querySelector('ul').click()
  154. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  155. document.querySelector('#outer').click()
  156. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(true) })
  157. document.querySelector('button').click()
  158. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  159. })
  160. test('supports short syntax', async () => {
  161. document.body.innerHTML = `
  162. <div x-data="{ foo: 'bar' }">
  163. <button @click="foo = 'baz'"></button>
  164. <span x-bind:foo="foo"></span>
  165. </div>
  166. `
  167. Alpine.start()
  168. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  169. document.querySelector('button').click()
  170. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  171. })
  172. test('event with colon', async () => {
  173. document.body.innerHTML = `
  174. <div x-data="{ foo: 'bar' }">
  175. <div x-on:my:event.document="foo = 'baz'"></div>
  176. <span x-bind:foo="foo"></span>
  177. </div>
  178. `
  179. Alpine.start()
  180. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  181. var event = new CustomEvent('my:event');
  182. document.dispatchEvent(event);
  183. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  184. })