on.spec.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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('click away', async () => {
  115. // Because jsDom doesn't support .offsetHeight and offsetWidth, we have to
  116. // make our own implementation using a specific class added to the class. Ugh.
  117. Object.defineProperties(window.HTMLElement.prototype, {
  118. offsetHeight: {
  119. get: function() { return this.classList.contains('hidden') ? 0 : 1 }
  120. },
  121. offsetWidth: {
  122. get: function() { return this.classList.contains('hidden') ? 0 : 1 }
  123. }
  124. });
  125. document.body.innerHTML = `
  126. <div id="outer">
  127. <div x-data="{ isOpen: true }">
  128. <button x-on:click="isOpen = true"></button>
  129. <ul x-bind:class="{ 'hidden': ! isOpen }" x-on:click.away="isOpen = false">
  130. <li>...</li>
  131. </ul>
  132. </div>
  133. </div>
  134. `
  135. Alpine.start()
  136. expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false)
  137. document.querySelector('li').click()
  138. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  139. document.querySelector('ul').click()
  140. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  141. document.querySelector('#outer').click()
  142. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(true) })
  143. document.querySelector('button').click()
  144. await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
  145. })
  146. test('supports short syntax', async () => {
  147. document.body.innerHTML = `
  148. <div x-data="{ foo: 'bar' }">
  149. <button @click="foo = 'baz'"></button>
  150. <span x-bind:foo="foo"></span>
  151. </div>
  152. `
  153. Alpine.start()
  154. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  155. document.querySelector('button').click()
  156. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  157. })
  158. test('event with colon', async () => {
  159. document.body.innerHTML = `
  160. <div x-data="{ foo: 'bar' }">
  161. <div x-on:my:event.document="foo = 'baz'"></div>
  162. <span x-bind:foo="foo"></span>
  163. </div>
  164. `
  165. Alpine.start()
  166. expect(document.querySelector('span').getAttribute('foo')).toEqual('bar')
  167. var event = new CustomEvent('my:event');
  168. document.dispatchEvent(event);
  169. await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
  170. })