1
0

x-on.spec.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  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(
  173. ".self.once modifiers",
  174. html`
  175. <div x-data="{ foo: 'bar' }">
  176. <h1 x-on:click.self.once="foo = 'baz'" id="selfTarget">
  177. content
  178. <button>click</button>
  179. content
  180. </h1>
  181. <span x-text="foo"></span>
  182. </div>
  183. `,
  184. ({ get }) => {
  185. get("span").should(haveText("bar"));
  186. get("button").click();
  187. get("span").should(haveText("bar"));
  188. get("h1").click();
  189. get("span").should(haveText("baz"));
  190. }
  191. );
  192. test('.prevent modifier',
  193. html`
  194. <div x-data="{}">
  195. <input type="checkbox" x-on:click.prevent>
  196. </div>
  197. `,
  198. ({ get }) => {
  199. get('input').check()
  200. get('input').should(notBeChecked())
  201. }
  202. )
  203. test('.prevent modifier with a .debounce',
  204. html`
  205. <div x-data="{}">
  206. <input type="checkbox" x-on:click.prevent.debounce>
  207. </div>
  208. `,
  209. ({ get }) => {
  210. get('input').check()
  211. get('input').check()
  212. get('input').should(notBeChecked())
  213. }
  214. )
  215. test('.window modifier',
  216. html`
  217. <div x-data="{ foo: 'bar' }">
  218. <div x-on:click.window="foo = 'baz'"></div>
  219. <span x-text="foo"></span>
  220. </div>
  221. `,
  222. ({ get }) => {
  223. get('span').should(haveText('bar'))
  224. get('span').click()
  225. get('span').should(haveText('baz'))
  226. }
  227. )
  228. test('expressions can start with if',
  229. html`
  230. <div x-data="{ foo: 'bar' }">
  231. <button @click="if (foo === 'bar') foo = 'baz'">click</button>
  232. <span x-text="foo"></span>
  233. </div>
  234. `,
  235. ({ get }) => {
  236. get('span').should(haveText('bar'))
  237. get('button').click()
  238. get('span').should(haveText('baz'))
  239. }
  240. )
  241. test('unbind global event handler when element is removed',
  242. html`
  243. <div x-data="{ count: 0 }">
  244. <div x-on:click.window="count++" x-ref="rmMe"></div>
  245. <button @click="$refs.rmMe.remove()">click</button>
  246. <span x-text="count"></span>
  247. </div>
  248. `,
  249. ({ get }) => {
  250. get('button').click()
  251. get('span').click()
  252. get('span').should(haveText('1'))
  253. }
  254. )
  255. test('.document modifier',
  256. html`
  257. <div x-data="{ foo: 'bar' }">
  258. <div x-on:click.document="foo = 'baz'"></div>
  259. <span x-text="foo"></span>
  260. </div>
  261. `,
  262. ({ get }) => {
  263. get('span').should(haveText('bar'))
  264. get('span').click()
  265. get('span').should(haveText('baz'))
  266. }
  267. )
  268. test('.once modifier',
  269. html`
  270. <div x-data="{ count: 0 }">
  271. <button x-on:click.once="count = count+1"></button>
  272. <span x-text="count"></span>
  273. </div>
  274. `,
  275. ({ get }) => {
  276. get('span').should(haveText('0'))
  277. get('button').click()
  278. get('span').should(haveText('1'))
  279. get('button').click()
  280. get('span').should(haveText('1'))
  281. }
  282. )
  283. test('.once modifier with @keyup',
  284. html`
  285. <div x-data="{ count: 0 }">
  286. <input type="text" x-on:keyup.once="count = count+1">
  287. <span x-text="count"></span>
  288. </div>
  289. `,
  290. ({ get }) => {
  291. get('span').should(haveText('0'))
  292. get('input').type('f')
  293. get('span').should(haveText('1'))
  294. get('input').type('o')
  295. get('span').should(haveText('1'))
  296. }
  297. )
  298. test('.once modifier with @keyup and specified key',
  299. html`
  300. <div x-data="{ count: 0 }">
  301. <input type="text" x-on:keyup.enter.once="count = count+1">
  302. <span x-text="count"></span>
  303. </div>
  304. `,
  305. ({ get }) => {
  306. get('span').should(haveText('0'))
  307. get('input').type('f')
  308. get('span').should(haveText('0'))
  309. get('input').type('{enter}')
  310. get('span').should(haveText('1'))
  311. get('input').type('{enter}')
  312. get('span').should(haveText('1'))
  313. }
  314. )
  315. test('.debounce modifier',
  316. html`
  317. <div x-data="{ count: 0 }">
  318. <input x-on:input.debounce="count = count+1">
  319. <span x-text="count"></span>
  320. </div>
  321. `,
  322. ({ get }) => {
  323. get('span').should(haveText('0'))
  324. get('input').type('f')
  325. get('span').should(haveText('1'))
  326. get('input').type('ffffffffffff')
  327. get('span').should(haveText('2'))
  328. }
  329. )
  330. test('.throttle modifier',
  331. html`
  332. <div x-data="{ count: 0 }">
  333. <input x-on:keyup.throttle.504ms="count = count+1">
  334. <span x-text="count"></span>
  335. </div>
  336. `,
  337. ({ get }) => {
  338. get('span').should(haveText('0'))
  339. get('input').type('f')
  340. get('span').should(haveText('1'))
  341. get('input').type('ffffffffffff')
  342. get('span').should(haveText('1'))
  343. }
  344. )
  345. test('keydown modifiers',
  346. html`
  347. <div x-data="{ count: 0 }">
  348. <input type="text"
  349. x-on:keydown="count++"
  350. x-on:keydown.enter="count++"
  351. x-on:keydown.space="count++"
  352. x-on:keydown.up="count++"
  353. x-on:keydown.down="count++"
  354. x-on:keydown.right="count++"
  355. x-on:keydown.left="count++"
  356. x-on:keydown.cmd="count++"
  357. x-on:keydown.meta="count++"
  358. x-on:keydown.escape="count++"
  359. x-on:keydown.esc="count++"
  360. x-on:keydown.ctrl="count++"
  361. x-on:keydown.slash="count++"
  362. x-on:keydown.period="count++"
  363. x-on:keydown.equal="count++"
  364. >
  365. <span x-text="count"></span>
  366. </div>
  367. `,
  368. ({ get }) => {
  369. get('span').should(haveText('0'))
  370. get('input').type('f')
  371. get('span').should(haveText('1'))
  372. get('input').type('{enter}')
  373. get('span').should(haveText('3'))
  374. get('input').type(' ')
  375. get('span').should(haveText('5'))
  376. get('input').type('{leftarrow}')
  377. get('span').should(haveText('7'))
  378. get('input').type('{rightarrow}')
  379. get('span').should(haveText('9'))
  380. get('input').type('{uparrow}')
  381. get('span').should(haveText('11'))
  382. get('input').type('{downarrow}')
  383. get('span').should(haveText('13'))
  384. get('input').type('{meta}')
  385. get('span').should(haveText('16'))
  386. get('input').type('{esc}')
  387. get('span').should(haveText('19'))
  388. get('input').type('{ctrl}')
  389. get('span').should(haveText('21'))
  390. get('input').type('/')
  391. get('span').should(haveText('23'))
  392. get('input').type('=')
  393. get('span').should(haveText('25'))
  394. get('input').type('.')
  395. get('span').should(haveText('27'))
  396. }
  397. )
  398. test('discerns between space minus underscore',
  399. html`
  400. <div x-data="{ count: 0 }">
  401. <input id="space" type="text" x-on:keydown.space="count++" />
  402. <input id="minus" type="text" x-on:keydown.-="count++" />
  403. <input id="underscore" type="text" x-on:keydown._="count++" />
  404. <span x-text="count"></span>
  405. </div>
  406. `,
  407. ({get}) => {
  408. get('span').should(haveText('0'))
  409. get('#space').type(' ')
  410. get('span').should(haveText('1'))
  411. get('#space').type('-')
  412. get('span').should(haveText('1'))
  413. get('#minus').type('-')
  414. get('span').should(haveText('2'))
  415. get('#minus').type(' ')
  416. get('span').should(haveText('2'))
  417. get('#underscore').type('_')
  418. get('span').should(haveText('3'))
  419. get('#underscore').type(' ')
  420. get('span').should(haveText('3'))
  421. })
  422. test('keydown combo modifiers',
  423. html`
  424. <div x-data="{ count: 0 }">
  425. <input type="text" x-on:keydown.cmd.enter="count++">
  426. <span x-text="count"></span>
  427. </div>
  428. `,
  429. ({ get }) => {
  430. get('span').should(haveText('0'))
  431. get('input').type('f')
  432. get('span').should(haveText('0'))
  433. get('input').type('{cmd}{enter}')
  434. get('span').should(haveText('1'))
  435. }
  436. )
  437. test('keydown with specified key and stop modifier only stops for specified key',
  438. html`
  439. <div x-data="{ count: 0 }">
  440. <article x-on:keydown="count++">
  441. <input type="text" x-on:keydown.enter.stop>
  442. </article>
  443. <span x-text="count"></span>
  444. </div>
  445. `,
  446. ({ get }) => {
  447. get('span').should(haveText('0'))
  448. get('input').type('f')
  449. get('span').should(haveText('1'))
  450. get('input').type('{enter}')
  451. get('span').should(haveText('1'))
  452. }
  453. )
  454. test('@click.away',
  455. html`
  456. <div x-data="{ foo: 'bar' }">
  457. <h1 @click.away="foo = 'baz'">h1</h1>
  458. <h2>h2</h2>
  459. <span x-text="foo"></span>
  460. </div>
  461. `,
  462. ({ get }) => {
  463. get('span').should(haveText('bar'))
  464. get('h1').click()
  465. get('span').should(haveText('bar'))
  466. get('h2').click()
  467. get('span').should(haveText('baz'))
  468. }
  469. )
  470. test('@click.away.once works after clicking inside',
  471. html`
  472. <div x-data="{ foo: 'bar' }">
  473. <h1 @click.away.once="foo = 'baz'">h1</h1>
  474. <h2>h2</h2>
  475. <span x-text="foo"></span>
  476. </div>
  477. `,
  478. ({ get }) => {
  479. get('span').should(haveText('bar'))
  480. get('h1').click()
  481. get('span').should(haveText('bar'))
  482. get('h2').click()
  483. get('span').should(haveText('baz'))
  484. }
  485. )
  486. test('@click.away with x-show (prevent race condition)',
  487. html`
  488. <div x-data="{ show: false }">
  489. <button @click="show = true">Show</button>
  490. <h1 x-show="show" @click.away="show = false">h1</h1>
  491. <h2>h2</h2>
  492. </div>
  493. `,
  494. ({ get }) => {
  495. get('h1').should(notBeVisible())
  496. get('button').click()
  497. get('h1').should(beVisible())
  498. }
  499. )
  500. test('event with colon',
  501. html`
  502. <div x-data="{ foo: 'bar' }">
  503. <div x-on:my:event.document="foo = 'baz'"></div>
  504. <button @click="document.dispatchEvent(new CustomEvent('my:event', { bubbles: true }))">click</button>
  505. <span x-text="foo"></span>
  506. </div>
  507. `,
  508. ({ get }) => {
  509. get('span').should(haveText('bar'))
  510. get('button').click()
  511. get('span').should(haveText('baz'))
  512. }
  513. )
  514. test('event instance can be passed to method reference',
  515. html`
  516. <div x-data="{ foo: 'bar', changeFoo(e) { this.foo = e.target.id } }">
  517. <button x-on:click="changeFoo" id="baz"></button>
  518. <span x-text="foo"></span>
  519. </div>
  520. `,
  521. ({ get }) => {
  522. get('span').should(haveText('bar'))
  523. get('button').click()
  524. get('span').should(haveText('baz'))
  525. }
  526. )
  527. test('.camel modifier correctly binds event listener',
  528. html`
  529. <div x-data="{ foo: 'bar' }" x-on:event-name.camel="foo = 'baz'">
  530. <button x-on:click="$dispatch('eventName')"></button>
  531. <span x-text="foo"></span>
  532. </div>
  533. `,
  534. ({ get }) => {
  535. get('span').should(haveText('bar'))
  536. get('button').click()
  537. get('span').should(haveText('baz'))
  538. }
  539. )
  540. test('.camel modifier correctly binds event listener with namespace',
  541. html`
  542. <div x-data="{ foo: 'bar' }" x-on:ns:event-name.camel="foo = 'baz'">
  543. <button x-on:click="$dispatch('ns:eventName')"></button>
  544. <span x-text="foo"></span>
  545. </div>
  546. `,
  547. ({ get }) => {
  548. get('span').should(haveText('bar'))
  549. get('button').click()
  550. get('span').should(haveText('baz'))
  551. }
  552. )
  553. test('.dot modifier correctly binds event listener',
  554. html`
  555. <div x-data="{ foo: 'bar' }" x-on:event-name.dot="foo = 'baz'">
  556. <button x-on:click="$dispatch('event.name')"></button>
  557. <span x-text="foo"></span>
  558. </div>
  559. `,
  560. ({ get }) => {
  561. get('span').should(haveText('bar'))
  562. get('button').click()
  563. get('span').should(haveText('baz'))
  564. }
  565. )
  566. test('underscores are allowed in event names',
  567. html`
  568. <div x-data="{ foo: 'bar' }" x-on:event_name="foo = 'baz'">
  569. <button x-on:click="$dispatch('event_name')"></button>
  570. <span x-text="foo"></span>
  571. </div>
  572. `,
  573. ({ get }) => {
  574. get('span').should(haveText('bar'))
  575. get('button').click()
  576. get('span').should(haveText('baz'))
  577. }
  578. )
  579. test('.dot modifier correctly binds event listener with namespace',
  580. html`
  581. <div x-data="{ foo: 'bar' }" x-on:ns:event-name.dot="foo = 'baz'">
  582. <button x-on:click="$dispatch('ns:event.name')"></button>
  583. <span x-text="foo"></span>
  584. </div>
  585. `,
  586. ({ get }) => {
  587. get('span').should(haveText('bar'))
  588. get('button').click()
  589. get('span').should(haveText('baz'))
  590. }
  591. )
  592. test('handles await in handlers with invalid right hand expressions',
  593. html`
  594. <div x-data="{ text: 'original' }">
  595. <button @click="let value = 'new string'; text = await Promise.resolve(value)"></button>
  596. <span x-text="text"></span>
  597. </div>
  598. `,
  599. ({ get }) => {
  600. get('span').should(haveText('original'))
  601. get('button').click()
  602. get('span').should(haveText('new string'))
  603. }
  604. )