x-on.spec.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. import { beChecked, notBeChecked, haveAttribute, haveData, haveText, test, beVisible, notBeVisible, html } from '../../utils'
  2. test('data modified in event listener updates affected attribute bindings',
  3. html`
  4. <div x-data="{ foo: 'bar' }">
  5. <button x-on:click="foo = 'baz'"></button>
  6. <span x-bind:foo="foo"></span>
  7. </div>
  8. `,
  9. ({ get }) => {
  10. get('span').should(haveAttribute('foo', 'bar'))
  11. get('button').click()
  12. get('span').should(haveAttribute('foo', 'baz'))
  13. }
  14. )
  15. test('can call a method without parenthesis',
  16. html`
  17. <div x-data="{ foo: 'bar', baz($event) { this.foo = $event.target.dataset.bob } }">
  18. <button x-on:click="baz" data-bob="lob"></button>
  19. <span x-text="foo"></span>
  20. </div>
  21. `,
  22. ({ get }) => {
  23. get('span').should(haveText('bar'))
  24. get('button').click()
  25. get('span').should(haveText('lob'))
  26. }
  27. )
  28. test('event object is not passed if other params are present',
  29. html`
  30. <div x-data="{ foo: 'bar', baz(word) { this.foo = word } }">
  31. <button x-on:click="baz('foo')" data-bob="lob"></button>
  32. <span x-text="foo"></span>
  33. </div>
  34. `,
  35. ({ get }) => {
  36. get('span').should(haveText('bar'))
  37. get('button').click()
  38. get('span').should(haveText('foo'))
  39. }
  40. )
  41. test('nested data modified in event listener updates affected attribute bindings',
  42. html`
  43. <div x-data="{ nested: { foo: 'bar' } }">
  44. <button x-on:click="nested.foo = 'baz'"></button>
  45. <span x-bind:foo="nested.foo"></span>
  46. </div>
  47. `,
  48. ({ get }) => {
  49. get('span').should(haveAttribute('foo', 'bar'))
  50. get('button').click()
  51. get('span').should(haveAttribute('foo', 'baz'))
  52. }
  53. )
  54. test('.passive modifier should disable e.preventDefault()',
  55. html`
  56. <div x-data="{ defaultPrevented: null }">
  57. <button
  58. x-on:mousedown.passive="
  59. $event.preventDefault();
  60. defaultPrevented = $event.defaultPrevented;
  61. "
  62. >
  63. <span></span>
  64. </button>
  65. </div>
  66. `,
  67. ({ get }) => {
  68. get('button').click()
  69. get('div').should(haveData('defaultPrevented', false))
  70. }
  71. )
  72. test('.stop modifier',
  73. html`
  74. <div x-data="{ foo: 'bar' }">
  75. <button x-on:click="foo = 'baz'">
  76. <h1>h1</h1>
  77. <h2 @click.stop>h2</h2>
  78. </button>
  79. </div>
  80. `,
  81. ({ get }) => {
  82. get('div').should(haveData('foo', 'bar'))
  83. get('h2').click()
  84. get('div').should(haveData('foo', 'bar'))
  85. get('h1').click()
  86. get('div').should(haveData('foo', 'baz'))
  87. }
  88. )
  89. test('.stop modifier with a .throttle',
  90. html`
  91. <div x-data="{ foo: 'bar' }">
  92. <button x-on:click="foo = 'baz'">
  93. <h1>h1</h1>
  94. <h2 @click.stop.throttle>h2</h2>
  95. </button>
  96. </div>
  97. `,
  98. ({ get }) => {
  99. get('div').should(haveData('foo', 'bar'))
  100. get('h2').click()
  101. get('h2').click()
  102. get('div').should(haveData('foo', 'bar'))
  103. get('h1').click()
  104. get('div').should(haveData('foo', 'baz'))
  105. }
  106. )
  107. test('.capture modifier',
  108. html`
  109. <div x-data="{ foo: 'bar', count: 0 }">
  110. <button @click.capture="count = count + 1; foo = 'baz'">
  111. <h1>h1</h1>
  112. <h2 @click="foo = 'bob'">h2</h2>
  113. </button>
  114. </div>
  115. `,
  116. ({ get }) => {
  117. get('div').should(haveData('foo', 'bar'))
  118. get('h2').click()
  119. get('div').should(haveData('foo', 'bob'))
  120. get('div').should(haveData('count', 1))
  121. }
  122. )
  123. test('.capture modifier with @keyup',
  124. html`
  125. <div x-data="{ foo: 'bar', count: 0 }">
  126. <span @keyup.capture="count = count + 1; foo = 'span'">
  127. <input type="text" @keyup="foo = 'input'">
  128. </span>
  129. </div>
  130. `,
  131. ({ get }) => {
  132. get('div').should(haveData('foo', 'bar'))
  133. get('input').type('f')
  134. get('div').should(haveData('foo', 'input'))
  135. get('div').should(haveData('count', 1))
  136. }
  137. )
  138. test('.capture modifier with @keyup and specified key',
  139. html`
  140. <div x-data="{ foo: 'bar', count: 0 }">
  141. <span @keyup.enter.capture="count = count + 1; foo = 'span'">
  142. <input type="text" @keyup.enter="foo = 'input'">
  143. </span>
  144. </div>
  145. `,
  146. ({ get }) => {
  147. get('div').should(haveData('foo', 'bar'))
  148. get('input').type('{enter}')
  149. get('div').should(haveData('foo', 'input'))
  150. get('div').should(haveData('count', 1))
  151. }
  152. )
  153. test('.self modifier',
  154. html`
  155. <div x-data="{ foo: 'bar' }">
  156. <h1 x-on:click.self="foo = 'baz'" id="selfTarget">
  157. content
  158. <button>click</button>
  159. content
  160. </h1>
  161. <span x-text="foo"></span>
  162. </div>
  163. `,
  164. ({ get }) => {
  165. get('span').should(haveText('bar'))
  166. get('button').click()
  167. get('span').should(haveText('bar'))
  168. get('h1').click()
  169. get('span').should(haveText('baz'))
  170. }
  171. )
  172. test('.prevent modifier',
  173. html`
  174. <div x-data="{}">
  175. <input type="checkbox" x-on:click.prevent>
  176. </div>
  177. `,
  178. ({ get }) => {
  179. get('input').check()
  180. get('input').should(notBeChecked())
  181. }
  182. )
  183. test('.prevent modifier with a .debounce',
  184. html`
  185. <div x-data="{}">
  186. <input type="checkbox" x-on:click.prevent.debounce>
  187. </div>
  188. `,
  189. ({ get }) => {
  190. get('input').check()
  191. get('input').check()
  192. get('input').should(notBeChecked())
  193. }
  194. )
  195. test('.window modifier',
  196. html`
  197. <div x-data="{ foo: 'bar' }">
  198. <div x-on:click.window="foo = 'baz'"></div>
  199. <span x-text="foo"></span>
  200. </div>
  201. `,
  202. ({ get }) => {
  203. get('span').should(haveText('bar'))
  204. get('span').click()
  205. get('span').should(haveText('baz'))
  206. }
  207. )
  208. test('expressions can start with if',
  209. html`
  210. <div x-data="{ foo: 'bar' }">
  211. <button @click="if (foo === 'bar') foo = 'baz'">click</button>
  212. <span x-text="foo"></span>
  213. </div>
  214. `,
  215. ({ get }) => {
  216. get('span').should(haveText('bar'))
  217. get('button').click()
  218. get('span').should(haveText('baz'))
  219. }
  220. )
  221. test('unbind global event handler when element is removed',
  222. html`
  223. <div x-data="{ count: 0 }">
  224. <div x-on:click.window="count++" x-ref="rmMe"></div>
  225. <button @click="$refs.rmMe.remove()">click</button>
  226. <span x-text="count"></span>
  227. </div>
  228. `,
  229. ({ get }) => {
  230. get('button').click()
  231. get('span').click()
  232. get('span').should(haveText('1'))
  233. }
  234. )
  235. test('.document modifier',
  236. html`
  237. <div x-data="{ foo: 'bar' }">
  238. <div x-on:click.document="foo = 'baz'"></div>
  239. <span x-text="foo"></span>
  240. </div>
  241. `,
  242. ({ get }) => {
  243. get('span').should(haveText('bar'))
  244. get('span').click()
  245. get('span').should(haveText('baz'))
  246. }
  247. )
  248. test('.once modifier',
  249. html`
  250. <div x-data="{ count: 0 }">
  251. <button x-on:click.once="count = count+1"></button>
  252. <span x-text="count"></span>
  253. </div>
  254. `,
  255. ({ get }) => {
  256. get('span').should(haveText('0'))
  257. get('button').click()
  258. get('span').should(haveText('1'))
  259. get('button').click()
  260. get('span').should(haveText('1'))
  261. }
  262. )
  263. test('.once modifier with @keyup',
  264. html`
  265. <div x-data="{ count: 0 }">
  266. <input type="text" x-on:keyup.once="count = count+1">
  267. <span x-text="count"></span>
  268. </div>
  269. `,
  270. ({ get }) => {
  271. get('span').should(haveText('0'))
  272. get('input').type('f')
  273. get('span').should(haveText('1'))
  274. get('input').type('o')
  275. get('span').should(haveText('1'))
  276. }
  277. )
  278. test('.once modifier with @keyup and specified key',
  279. html`
  280. <div x-data="{ count: 0 }">
  281. <input type="text" x-on:keyup.enter.once="count = count+1">
  282. <span x-text="count"></span>
  283. </div>
  284. `,
  285. ({ get }) => {
  286. get('span').should(haveText('0'))
  287. get('input').type('f')
  288. get('span').should(haveText('0'))
  289. get('input').type('{enter}')
  290. get('span').should(haveText('1'))
  291. get('input').type('{enter}')
  292. get('span').should(haveText('1'))
  293. }
  294. )
  295. test('.debounce modifier',
  296. html`
  297. <div x-data="{ count: 0 }">
  298. <input x-on:input.debounce="count = count+1">
  299. <span x-text="count"></span>
  300. </div>
  301. `,
  302. ({ get }) => {
  303. get('span').should(haveText('0'))
  304. get('input').type('f')
  305. get('span').should(haveText('1'))
  306. get('input').type('ffffffffffff')
  307. get('span').should(haveText('2'))
  308. }
  309. )
  310. test('.throttle modifier',
  311. html`
  312. <div x-data="{ count: 0 }">
  313. <input x-on:keyup.throttle.504ms="count = count+1">
  314. <span x-text="count"></span>
  315. </div>
  316. `,
  317. ({ get }) => {
  318. get('span').should(haveText('0'))
  319. get('input').type('f')
  320. get('span').should(haveText('1'))
  321. get('input').type('ffffffffffff')
  322. get('span').should(haveText('1'))
  323. }
  324. )
  325. test('keydown modifiers',
  326. html`
  327. <div x-data="{ count: 0 }">
  328. <input type="text"
  329. x-on:keydown="count++"
  330. x-on:keydown.enter="count++"
  331. x-on:keydown.space="count++"
  332. x-on:keydown.up="count++"
  333. x-on:keydown.down="count++"
  334. x-on:keydown.right="count++"
  335. x-on:keydown.left="count++"
  336. x-on:keydown.cmd="count++"
  337. x-on:keydown.meta="count++"
  338. x-on:keydown.escape="count++"
  339. x-on:keydown.esc="count++"
  340. x-on:keydown.ctrl="count++"
  341. x-on:keydown.slash="count++"
  342. x-on:keydown.period="count++"
  343. x-on:keydown.equal="count++"
  344. >
  345. <span x-text="count"></span>
  346. </div>
  347. `,
  348. ({ get }) => {
  349. get('span').should(haveText('0'))
  350. get('input').type('f')
  351. get('span').should(haveText('1'))
  352. get('input').type('{enter}')
  353. get('span').should(haveText('3'))
  354. get('input').type(' ')
  355. get('span').should(haveText('5'))
  356. get('input').type('{leftarrow}')
  357. get('span').should(haveText('7'))
  358. get('input').type('{rightarrow}')
  359. get('span').should(haveText('9'))
  360. get('input').type('{uparrow}')
  361. get('span').should(haveText('11'))
  362. get('input').type('{downarrow}')
  363. get('span').should(haveText('13'))
  364. get('input').type('{meta}')
  365. get('span').should(haveText('16'))
  366. get('input').type('{esc}')
  367. get('span').should(haveText('19'))
  368. get('input').type('{ctrl}')
  369. get('span').should(haveText('21'))
  370. get('input').type('/')
  371. get('span').should(haveText('23'))
  372. get('input').type('=')
  373. get('span').should(haveText('25'))
  374. get('input').type('.')
  375. get('span').should(haveText('27'))
  376. }
  377. )
  378. test('discerns between space minus underscore',
  379. html`
  380. <div x-data="{ count: 0 }">
  381. <input id="space" type="text" x-on:keydown.space="count++" />
  382. <input id="minus" type="text" x-on:keydown.-="count++" />
  383. <input id="underscore" type="text" x-on:keydown._="count++" />
  384. <span x-text="count"></span>
  385. </div>
  386. `,
  387. ({get}) => {
  388. get('span').should(haveText('0'))
  389. get('#space').type(' ')
  390. get('span').should(haveText('1'))
  391. get('#space').type('-')
  392. get('span').should(haveText('1'))
  393. get('#minus').type('-')
  394. get('span').should(haveText('2'))
  395. get('#minus').type(' ')
  396. get('span').should(haveText('2'))
  397. get('#underscore').type('_')
  398. get('span').should(haveText('3'))
  399. get('#underscore').type(' ')
  400. get('span').should(haveText('3'))
  401. })
  402. test('keydown combo modifiers',
  403. html`
  404. <div x-data="{ count: 0 }">
  405. <input type="text" x-on:keydown.cmd.enter="count++">
  406. <span x-text="count"></span>
  407. </div>
  408. `,
  409. ({ get }) => {
  410. get('span').should(haveText('0'))
  411. get('input').type('f')
  412. get('span').should(haveText('0'))
  413. get('input').type('{cmd}{enter}')
  414. get('span').should(haveText('1'))
  415. }
  416. )
  417. test('keydown with specified key and stop modifier only stops for specified key',
  418. html`
  419. <div x-data="{ count: 0 }">
  420. <article x-on:keydown="count++">
  421. <input type="text" x-on:keydown.enter.stop>
  422. </article>
  423. <span x-text="count"></span>
  424. </div>
  425. `,
  426. ({ get }) => {
  427. get('span').should(haveText('0'))
  428. get('input').type('f')
  429. get('span').should(haveText('1'))
  430. get('input').type('{enter}')
  431. get('span').should(haveText('1'))
  432. }
  433. )
  434. test('@click.away',
  435. html`
  436. <div x-data="{ foo: 'bar' }">
  437. <h1 @click.away="foo = 'baz'">h1</h1>
  438. <h2>h2</h2>
  439. <span x-text="foo"></span>
  440. </div>
  441. `,
  442. ({ get }) => {
  443. get('span').should(haveText('bar'))
  444. get('h1').click()
  445. get('span').should(haveText('bar'))
  446. get('h2').click()
  447. get('span').should(haveText('baz'))
  448. }
  449. )
  450. test('@click.away with x-show (prevent race condition)',
  451. html`
  452. <div x-data="{ show: false }">
  453. <button @click="show = true">Show</button>
  454. <h1 x-show="show" @click.away="show = false">h1</h1>
  455. <h2>h2</h2>
  456. </div>
  457. `,
  458. ({ get }) => {
  459. get('h1').should(notBeVisible())
  460. get('button').click()
  461. get('h1').should(beVisible())
  462. }
  463. )
  464. test('event with colon',
  465. html`
  466. <div x-data="{ foo: 'bar' }">
  467. <div x-on:my:event.document="foo = 'baz'"></div>
  468. <button @click="document.dispatchEvent(new CustomEvent('my:event', { bubbles: true }))">click</button>
  469. <span x-text="foo"></span>
  470. </div>
  471. `,
  472. ({ get }) => {
  473. get('span').should(haveText('bar'))
  474. get('button').click()
  475. get('span').should(haveText('baz'))
  476. }
  477. )
  478. test('event instance can be passed to method reference',
  479. html`
  480. <div x-data="{ foo: 'bar', changeFoo(e) { this.foo = e.target.id } }">
  481. <button x-on:click="changeFoo" id="baz"></button>
  482. <span x-text="foo"></span>
  483. </div>
  484. `,
  485. ({ get }) => {
  486. get('span').should(haveText('bar'))
  487. get('button').click()
  488. get('span').should(haveText('baz'))
  489. }
  490. )
  491. test('.camel modifier correctly binds event listener',
  492. html`
  493. <div x-data="{ foo: 'bar' }" x-on:event-name.camel="foo = 'baz'">
  494. <button x-on:click="$dispatch('eventName')"></button>
  495. <span x-text="foo"></span>
  496. </div>
  497. `,
  498. ({ get }) => {
  499. get('span').should(haveText('bar'))
  500. get('button').click()
  501. get('span').should(haveText('baz'))
  502. }
  503. )
  504. test('.camel modifier correctly binds event listener with namespace',
  505. html`
  506. <div x-data="{ foo: 'bar' }" x-on:ns:event-name.camel="foo = 'baz'">
  507. <button x-on:click="$dispatch('ns:eventName')"></button>
  508. <span x-text="foo"></span>
  509. </div>
  510. `,
  511. ({ get }) => {
  512. get('span').should(haveText('bar'))
  513. get('button').click()
  514. get('span').should(haveText('baz'))
  515. }
  516. )
  517. test('.dot modifier correctly binds event listener',
  518. html`
  519. <div x-data="{ foo: 'bar' }" x-on:event-name.dot="foo = 'baz'">
  520. <button x-on:click="$dispatch('event.name')"></button>
  521. <span x-text="foo"></span>
  522. </div>
  523. `,
  524. ({ get }) => {
  525. get('span').should(haveText('bar'))
  526. get('button').click()
  527. get('span').should(haveText('baz'))
  528. }
  529. )
  530. test('underscores are allowed in event names',
  531. html`
  532. <div x-data="{ foo: 'bar' }" x-on:event_name="foo = 'baz'">
  533. <button x-on:click="$dispatch('event_name')"></button>
  534. <span x-text="foo"></span>
  535. </div>
  536. `,
  537. ({ get }) => {
  538. get('span').should(haveText('bar'))
  539. get('button').click()
  540. get('span').should(haveText('baz'))
  541. }
  542. )
  543. test('.dot modifier correctly binds event listener with namespace',
  544. html`
  545. <div x-data="{ foo: 'bar' }" x-on:ns:event-name.dot="foo = 'baz'">
  546. <button x-on:click="$dispatch('ns:event.name')"></button>
  547. <span x-text="foo"></span>
  548. </div>
  549. `,
  550. ({ get }) => {
  551. get('span').should(haveText('bar'))
  552. get('button').click()
  553. get('span').should(haveText('baz'))
  554. }
  555. )
  556. test('handles await in handlers with invalid right hand expressions',
  557. html`
  558. <div x-data="{ text: 'original' }">
  559. <button @click="let value = 'new string'; text = await Promise.resolve(value)"></button>
  560. <span x-text="text"></span>
  561. </div>
  562. `,
  563. ({ get }) => {
  564. get('span').should(haveText('original'))
  565. get('button').click()
  566. get('span').should(haveText('new string'))
  567. }
  568. )