listbox.spec.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. import { beVisible, beHidden, haveAttribute, haveClasses, notHaveClasses, haveText, html, notBeVisible, notHaveAttribute, notExist, haveFocus, test} from '../../../utils'
  2. test('it works with x-model',
  3. [html`
  4. <div
  5. x-data="{ active: null, people: [
  6. { id: 1, name: 'Wade Cooper' },
  7. { id: 2, name: 'Arlene Mccoy' },
  8. { id: 3, name: 'Devon Webb' },
  9. { id: 4, name: 'Tom Cook' },
  10. { id: 5, name: 'Tanya Fox', disabled: true },
  11. { id: 6, name: 'Hellen Schmidt' },
  12. { id: 7, name: 'Caroline Schultz' },
  13. { id: 8, name: 'Mason Heaney' },
  14. { id: 9, name: 'Claudie Smitham' },
  15. { id: 10, name: 'Emil Schaefer' },
  16. ]}"
  17. x-listbox
  18. x-model="active"
  19. >
  20. <label x-listbox:label>Assigned to</label>
  21. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  22. <ul x-listbox:options>
  23. <template x-for="person in people" :key="person.id">
  24. <li
  25. :option="person.id"
  26. x-listbox:option
  27. :value="person"
  28. :disabled="person.disabled"
  29. >
  30. <span x-text="person.name"></span>
  31. </li>
  32. </template>
  33. </ul>
  34. </div>
  35. `],
  36. ({ get }) => {
  37. get('ul').should(notBeVisible())
  38. get('button')
  39. .should(haveText('Select Person'))
  40. .click()
  41. get('ul').should(beVisible())
  42. get('button').click()
  43. get('ul').should(notBeVisible())
  44. get('button').click()
  45. get('[option="2"]').click()
  46. get('ul').should(notBeVisible())
  47. get('button').should(haveText('Arlene Mccoy'))
  48. },
  49. )
  50. test('it works with internal state',
  51. [html`
  52. <div
  53. x-data="{ people: [
  54. { id: 1, name: 'Wade Cooper' },
  55. { id: 2, name: 'Arlene Mccoy' },
  56. { id: 3, name: 'Devon Webb' },
  57. { id: 4, name: 'Tom Cook' },
  58. { id: 5, name: 'Tanya Fox', disabled: true },
  59. { id: 6, name: 'Hellen Schmidt' },
  60. { id: 7, name: 'Caroline Schultz' },
  61. { id: 8, name: 'Mason Heaney' },
  62. { id: 9, name: 'Claudie Smitham' },
  63. { id: 10, name: 'Emil Schaefer' },
  64. ]}"
  65. x-listbox
  66. >
  67. <label x-listbox:label>Assigned to</label>
  68. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected.name : 'Select Person'"></button>
  69. <ul x-listbox:options>
  70. <template x-for="person in people" :key="person.id">
  71. <li
  72. :option="person.id"
  73. x-listbox:option
  74. :value="person"
  75. :disabled="person.disabled"
  76. >
  77. <span x-text="person.name"></span>
  78. </li>
  79. </template>
  80. </ul>
  81. </div>
  82. `],
  83. ({ get }) => {
  84. get('ul').should(notBeVisible())
  85. get('button')
  86. .should(haveText('Select Person'))
  87. .click()
  88. get('ul').should(beVisible())
  89. get('button').click()
  90. get('ul').should(notBeVisible())
  91. get('button').click()
  92. get('[option="2"]').click()
  93. get('ul').should(notBeVisible())
  94. get('button').should(haveText('Arlene Mccoy'))
  95. },
  96. )
  97. test('$listbox/$listboxOption',
  98. [html`
  99. <div
  100. x-data="{ people: [
  101. { id: 1, name: 'Wade Cooper' },
  102. { id: 2, name: 'Arlene Mccoy' },
  103. { id: 3, name: 'Devon Webb' },
  104. { id: 4, name: 'Tom Cook' },
  105. { id: 5, name: 'Tanya Fox', disabled: true },
  106. { id: 6, name: 'Hellen Schmidt' },
  107. { id: 7, name: 'Caroline Schultz' },
  108. { id: 8, name: 'Mason Heaney' },
  109. { id: 9, name: 'Claudie Smitham' },
  110. { id: 10, name: 'Emil Schaefer' },
  111. ]}"
  112. x-listbox
  113. >
  114. <label x-listbox:label>Assigned to</label>
  115. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected.name : 'Select Person'"></button>
  116. <article x-text="$listbox.active?.name"></article>
  117. <ul x-listbox:options>
  118. <template x-for="person in people" :key="person.id">
  119. <li
  120. :option="person.id"
  121. x-listbox:option
  122. :value="person"
  123. :disabled="person.disabled"
  124. :class="{
  125. 'selected': $listboxOption.isSelected,
  126. 'active': $listboxOption.isActive,
  127. 'disabled': $listboxOption.isDisabled,
  128. }"
  129. >
  130. <span x-text="person.name"></span>
  131. </li>
  132. </template>
  133. </ul>
  134. </div>
  135. `],
  136. ({ get }) => {
  137. get('article').should(haveText(''))
  138. get('[option="5"]').should(haveClasses(['disabled']))
  139. get('button')
  140. .should(haveText('Select Person'))
  141. .click()
  142. get('article').should(haveText('Wade Cooper'))
  143. get('[option="1"]').should(haveClasses(['active']))
  144. get('ul').type('{downarrow}')
  145. get('article').should(haveText('Arlene Mccoy'))
  146. get('[option="2"]').should(haveClasses(['active']))
  147. get('button').should(haveText('Select Person'))
  148. get('[option="2"]').click()
  149. get('button').should(haveText('Arlene Mccoy'))
  150. get('[option="2"]').should(haveClasses(['selected']))
  151. },
  152. )
  153. test('"name" prop',
  154. [html`
  155. <div
  156. x-data="{ people: [
  157. { id: 1, name: 'Wade Cooper' },
  158. { id: 2, name: 'Arlene Mccoy' },
  159. { id: 3, name: 'Devon Webb' },
  160. { id: 4, name: 'Tom Cook' },
  161. { id: 5, name: 'Tanya Fox', disabled: true },
  162. { id: 6, name: 'Hellen Schmidt' },
  163. { id: 7, name: 'Caroline Schultz' },
  164. { id: 8, name: 'Mason Heaney' },
  165. { id: 9, name: 'Claudie Smitham' },
  166. { id: 10, name: 'Emil Schaefer' },
  167. ]}"
  168. x-listbox
  169. name="person"
  170. >
  171. <label x-listbox:label>Assigned to</label>
  172. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected : 'Select Person'"></button>
  173. <ul x-listbox:options>
  174. <template x-for="person in people" :key="person.id">
  175. <li
  176. :option="person.id"
  177. x-listbox:option
  178. :value="person.id"
  179. :disabled="person.disabled"
  180. :class="{
  181. 'selected': $listboxOption.isSelected,
  182. 'active': $listboxOption.isActive,
  183. }"
  184. >
  185. <span x-text="person.name"></span>
  186. </li>
  187. </template>
  188. </ul>
  189. </div>
  190. `],
  191. ({ get }) => {
  192. get('input').should(haveAttribute('value', 'null'))
  193. get('button').click()
  194. get('input').should(haveAttribute('value', 'null'))
  195. get('[option="2"]').click()
  196. get('input').should(beHidden())
  197. .should(haveAttribute('name', 'person'))
  198. .should(haveAttribute('value', '2'))
  199. .should(haveAttribute('type', 'hidden'))
  200. get('button').click()
  201. get('[option="4"]').click()
  202. get('input').should(beHidden())
  203. .should(haveAttribute('name', 'person'))
  204. .should(haveAttribute('value', '4'))
  205. .should(haveAttribute('type', 'hidden'))
  206. },
  207. );
  208. test('"name" prop with object value',
  209. [html`
  210. <div
  211. x-data="{ people: [
  212. { id: 1, name: 'Wade Cooper' },
  213. { id: 2, name: 'Arlene Mccoy' },
  214. { id: 3, name: 'Devon Webb' },
  215. { id: 4, name: 'Tom Cook' },
  216. { id: 5, name: 'Tanya Fox', disabled: true },
  217. { id: 6, name: 'Hellen Schmidt' },
  218. { id: 7, name: 'Caroline Schultz' },
  219. { id: 8, name: 'Mason Heaney' },
  220. { id: 9, name: 'Claudie Smitham' },
  221. { id: 10, name: 'Emil Schaefer' },
  222. ]}"
  223. x-listbox
  224. name="person"
  225. >
  226. <label x-listbox:label>Assigned to</label>
  227. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected.name : 'Select Person'"></button>
  228. <ul x-listbox:options>
  229. <template x-for="person in people" :key="person.id">
  230. <li
  231. :option="person.id"
  232. x-listbox:option
  233. :value="person"
  234. :disabled="person.disabled"
  235. :class="{
  236. 'selected': $listboxOption.isSelected,
  237. 'active': $listboxOption.isActive,
  238. }"
  239. >
  240. <span x-text="person.name"></span>
  241. </li>
  242. </template>
  243. </ul>
  244. </div>
  245. `],
  246. ({ get }) => {
  247. get('input[name="person"]').should(haveAttribute('value', 'null'))
  248. get('button').click()
  249. get('[name="person[id]"]').should(notExist())
  250. get('[option="2"]').click()
  251. get('input[name="person"]').should(notExist())
  252. get('[name="person[id]"]').should(beHidden())
  253. .should(haveAttribute('value', '2'))
  254. .should(haveAttribute('type', 'hidden'))
  255. get('[name="person[name]"]').should(beHidden())
  256. .should(haveAttribute('value', 'Arlene Mccoy'))
  257. .should(haveAttribute('type', 'hidden'))
  258. get('button').click()
  259. get('[option="4"]').click()
  260. get('[name="person[id]"]').should(beHidden())
  261. .should(haveAttribute('value', '4'))
  262. .should(haveAttribute('type', 'hidden'))
  263. get('[name="person[name]"]').should(beHidden())
  264. .should(haveAttribute('value', 'Tom Cook'))
  265. .should(haveAttribute('type', 'hidden'))
  266. },
  267. );
  268. test('"default-value" prop',
  269. [html`
  270. <div
  271. x-data="{ people: [
  272. { id: 1, name: 'Wade Cooper' },
  273. { id: 2, name: 'Arlene Mccoy' },
  274. { id: 3, name: 'Devon Webb' },
  275. { id: 4, name: 'Tom Cook' },
  276. { id: 5, name: 'Tanya Fox', disabled: true },
  277. { id: 6, name: 'Hellen Schmidt' },
  278. { id: 7, name: 'Caroline Schultz' },
  279. { id: 8, name: 'Mason Heaney' },
  280. { id: 9, name: 'Claudie Smitham' },
  281. { id: 10, name: 'Emil Schaefer' },
  282. ]}"
  283. x-listbox
  284. name="person"
  285. default-value="2"
  286. >
  287. <label x-listbox:label>Assigned to</label>
  288. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected : 'Select Person'"></button>
  289. <ul x-listbox:options>
  290. <template x-for="person in people" :key="person.id">
  291. <li
  292. :option="person.id"
  293. x-listbox:option
  294. :value="person.id"
  295. :disabled="person.disabled"
  296. :class="{
  297. 'selected': $listboxOption.isSelected,
  298. 'active': $listboxOption.isActive,
  299. }"
  300. >
  301. <span x-text="person.name"></span>
  302. </li>
  303. </template>
  304. </ul>
  305. </div>
  306. `],
  307. ({ get }) => {
  308. get('input').should(beHidden())
  309. .should(haveAttribute('name', 'person'))
  310. .should(haveAttribute('value', '2'))
  311. .should(haveAttribute('type', 'hidden'))
  312. },
  313. );
  314. test('"multiple" prop',
  315. [html`
  316. <div
  317. x-data="{
  318. people: [
  319. { id: 1, name: 'Wade Cooper' },
  320. { id: 2, name: 'Arlene Mccoy' },
  321. { id: 3, name: 'Devon Webb' },
  322. { id: 4, name: 'Tom Cook' },
  323. { id: 5, name: 'Tanya Fox', disabled: true },
  324. { id: 6, name: 'Hellen Schmidt' },
  325. { id: 7, name: 'Caroline Schultz' },
  326. { id: 8, name: 'Mason Heaney' },
  327. { id: 9, name: 'Claudie Smitham' },
  328. { id: 10, name: 'Emil Schaefer' },
  329. ]
  330. }"
  331. x-listbox
  332. multiple
  333. >
  334. <label x-listbox:label>Assigned to</label>
  335. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected.join(',') : 'Select People'"></button>
  336. <ul x-listbox:options>
  337. <template x-for="person in people" :key="person.id">
  338. <li
  339. :option="person.id"
  340. x-listbox:option
  341. :value="person.id"
  342. :disabled="person.disabled"
  343. :class="{
  344. 'selected': $listboxOption.isSelected,
  345. 'active': $listboxOption.isActive,
  346. }"
  347. >
  348. <span x-text="person.name"></span>
  349. </li>
  350. </template>
  351. </ul>
  352. </div>
  353. `],
  354. ({ get }) => {
  355. get('button').click()
  356. get('[option="2"]').click()
  357. get('ul').should(beVisible())
  358. get('button').should(haveText('2'))
  359. get('[option="4"]').click()
  360. get('button').should(haveText('2,4'))
  361. get('ul').should(beVisible())
  362. get('[option="4"]').click()
  363. get('button').should(haveText('2'))
  364. get('ul').should(beVisible())
  365. },
  366. );
  367. test('"multiple" and "name" props together',
  368. [html`
  369. <div
  370. x-data="{
  371. people: [
  372. { id: 1, name: 'Wade Cooper' },
  373. { id: 2, name: 'Arlene Mccoy' },
  374. { id: 3, name: 'Devon Webb' },
  375. { id: 4, name: 'Tom Cook' },
  376. { id: 5, name: 'Tanya Fox', disabled: true },
  377. { id: 6, name: 'Hellen Schmidt' },
  378. { id: 7, name: 'Caroline Schultz' },
  379. { id: 8, name: 'Mason Heaney' },
  380. { id: 9, name: 'Claudie Smitham' },
  381. { id: 10, name: 'Emil Schaefer' },
  382. ]
  383. }"
  384. x-listbox
  385. multiple
  386. name="people"
  387. >
  388. <label x-listbox:label>Assigned to</label>
  389. <button x-listbox:button x-text="$listbox.selected ? $listbox.selected.map(p => p.id).join(',') : 'Select People'"></button>
  390. <ul x-listbox:options>
  391. <template x-for="person in people" :key="person.id">
  392. <li
  393. :option="person.id"
  394. x-listbox:option
  395. :value="person"
  396. :disabled="person.disabled"
  397. :class="{
  398. 'selected': $listboxOption.isSelected,
  399. 'active': $listboxOption.isActive,
  400. }"
  401. >
  402. <span x-text="person.name"></span>
  403. </li>
  404. </template>
  405. </ul>
  406. </div>
  407. `],
  408. ({ get }) => {
  409. get('input[name="people"]').should(notExist())
  410. get('button').click()
  411. get('[name="people[0][id]"]').should(notExist())
  412. get('[option="2"]').click()
  413. get('ul').should(beVisible())
  414. get('button').should(haveText('2'))
  415. get('input[name="people"]').should(notExist())
  416. get('[name="people[0][id]"]').should(beHidden())
  417. .should(haveAttribute('value', '2'))
  418. .should(haveAttribute('type', 'hidden'))
  419. get('[name="people[0][name]"]').should(beHidden())
  420. .should(haveAttribute('value', 'Arlene Mccoy'))
  421. .should(haveAttribute('type', 'hidden'))
  422. get('[option="4"]').click()
  423. get('[name="people[0][id]"]').should(beHidden())
  424. .should(haveAttribute('value', '2'))
  425. .should(haveAttribute('type', 'hidden'))
  426. get('[name="people[0][name]"]').should(beHidden())
  427. .should(haveAttribute('value', 'Arlene Mccoy'))
  428. .should(haveAttribute('type', 'hidden'))
  429. get('[name="people[1][id]"]').should(beHidden())
  430. .should(haveAttribute('value', '4'))
  431. .should(haveAttribute('type', 'hidden'))
  432. get('[name="people[1][name]"]').should(beHidden())
  433. .should(haveAttribute('value', 'Tom Cook'))
  434. .should(haveAttribute('type', 'hidden'))
  435. get('button').should(haveText('2,4'))
  436. get('ul').should(beVisible())
  437. get('[option="4"]').click()
  438. get('[name="people[0][id]"]').should(beHidden())
  439. .should(haveAttribute('value', '2'))
  440. .should(haveAttribute('type', 'hidden'))
  441. get('[name="people[0][name]"]').should(beHidden())
  442. .should(haveAttribute('value', 'Arlene Mccoy'))
  443. .should(haveAttribute('type', 'hidden'))
  444. get('[name="people[1][id]"]').should(notExist())
  445. get('[name="people[1][name]"]').should(notExist())
  446. get('button').should(haveText('2'))
  447. get('ul').should(beVisible())
  448. },
  449. );
  450. test('keyboard controls',
  451. [html`
  452. <div
  453. x-data="{ active: null, people: [
  454. { id: 1, name: 'Wade Cooper' },
  455. { id: 2, name: 'Arlene Mccoy' },
  456. { id: 3, name: 'Devon Webb', disabled: true },
  457. { id: 4, name: 'Tom Cook' },
  458. { id: 5, name: 'Tanya Fox', disabled: true },
  459. { id: 6, name: 'Hellen Schmidt' },
  460. { id: 7, name: 'Caroline Schultz' },
  461. { id: 8, name: 'Mason Heaney' },
  462. { id: 9, name: 'Claudie Smitham' },
  463. { id: 10, name: 'Emil Schaefer' },
  464. ]}"
  465. x-listbox
  466. x-model="active"
  467. >
  468. <label x-listbox:label>Assigned to</label>
  469. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  470. <ul x-listbox:options options>
  471. <template x-for="person in people" :key="person.id">
  472. <li
  473. :option="person.id"
  474. x-listbox:option
  475. :value="person"
  476. :disabled="person.disabled"
  477. :class="{
  478. 'selected': $listboxOption.isSelected,
  479. 'active': $listboxOption.isActive,
  480. }"
  481. >
  482. <span x-text="person.name"></span>
  483. </li>
  484. </template>
  485. </ul>
  486. </div>
  487. `],
  488. ({ get }) => {
  489. get('.active').should(notExist())
  490. get('button').focus().type(' ')
  491. get('[options]')
  492. .should(beVisible())
  493. .should(haveFocus())
  494. get('[option="1"]')
  495. .should(haveClasses(['active']))
  496. get('[options]')
  497. .type('{downarrow}')
  498. get('[option="2"]')
  499. .should(haveClasses(['active']))
  500. get('[options]')
  501. .type('{downarrow}')
  502. get('[option="4"]')
  503. .should(haveClasses(['active']))
  504. get('[options]')
  505. .type('{uparrow}')
  506. get('[option="2"]')
  507. .should(haveClasses(['active']))
  508. get('[options]')
  509. .type('{home}')
  510. get('[option="1"]')
  511. .should(haveClasses(['active']))
  512. get('[options]')
  513. .type('{end}')
  514. get('[option="10"]')
  515. .should(haveClasses(['active']))
  516. get('[options]')
  517. .type('{pageUp}')
  518. get('[option="1"]')
  519. .should(haveClasses(['active']))
  520. get('[options]')
  521. .type('{pageDown}')
  522. get('[option="10"]')
  523. .should(haveClasses(['active']))
  524. get('[options]')
  525. .tab()
  526. .should(haveFocus())
  527. .should(beVisible())
  528. .tab({ shift: true })
  529. .should(haveFocus())
  530. .should(beVisible())
  531. .type('{esc}')
  532. .should(notBeVisible())
  533. },
  534. )
  535. test('"horizontal" keyboard controls',
  536. [html`
  537. <div
  538. x-data="{ active: null, people: [
  539. { id: 1, name: 'Wade Cooper' },
  540. { id: 2, name: 'Arlene Mccoy' },
  541. { id: 3, name: 'Devon Webb', disabled: true },
  542. { id: 4, name: 'Tom Cook' },
  543. { id: 5, name: 'Tanya Fox', disabled: true },
  544. { id: 6, name: 'Hellen Schmidt' },
  545. { id: 7, name: 'Caroline Schultz' },
  546. { id: 8, name: 'Mason Heaney' },
  547. { id: 9, name: 'Claudie Smitham' },
  548. { id: 10, name: 'Emil Schaefer' },
  549. ]}"
  550. x-listbox
  551. x-model="active"
  552. horizontal
  553. >
  554. <label x-listbox:label>Assigned to</label>
  555. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  556. <ul x-listbox:options options>
  557. <template x-for="person in people" :key="person.id">
  558. <li
  559. :option="person.id"
  560. x-listbox:option
  561. :value="person"
  562. :disabled="person.disabled"
  563. :class="{
  564. 'selected': $listboxOption.isSelected,
  565. 'active': $listboxOption.isActive,
  566. }"
  567. >
  568. <span x-text="person.name"></span>
  569. </li>
  570. </template>
  571. </ul>
  572. </div>
  573. `],
  574. ({ get }) => {
  575. get('.active').should(notExist())
  576. get('button').focus().type(' ')
  577. get('[options]')
  578. .should(haveAttribute('aria-orientation', 'horizontal'))
  579. .should(beVisible())
  580. .should(haveFocus())
  581. get('[option="1"]')
  582. .should(haveClasses(['active']))
  583. get('[options]')
  584. .type('{rightarrow}')
  585. get('[option="2"]')
  586. .should(haveClasses(['active']))
  587. get('[options]')
  588. .type('{rightarrow}')
  589. get('[option="4"]')
  590. .should(haveClasses(['active']))
  591. get('[options]')
  592. .type('{leftarrow}')
  593. get('[option="2"]')
  594. .should(haveClasses(['active']))
  595. },
  596. )
  597. test('search',
  598. [html`
  599. <div
  600. x-data="{ active: null, people: [
  601. { id: 1, name: 'Wade Cooper' },
  602. { id: 2, name: 'Arlene Mccoy' },
  603. { id: 3, name: 'Devon Webb', disabled: true },
  604. { id: 4, name: 'Tom Cook' },
  605. { id: 5, name: 'Tanya Fox', disabled: true },
  606. { id: 6, name: 'Hellen Schmidt' },
  607. { id: 7, name: 'Caroline Schultz' },
  608. { id: 8, name: 'Mason Heaney' },
  609. { id: 9, name: 'Claudie Smitham' },
  610. { id: 10, name: 'Emil Schaefer' },
  611. ]}"
  612. x-listbox
  613. x-model="active"
  614. >
  615. <label x-listbox:label>Assigned to</label>
  616. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  617. <ul x-listbox:options options>
  618. <template x-for="person in people" :key="person.id">
  619. <li
  620. :option="person.id"
  621. x-listbox:option
  622. :value="person"
  623. :disabled="person.disabled"
  624. :class="{
  625. 'selected': $listboxOption.isSelected,
  626. 'active': $listboxOption.isActive,
  627. }"
  628. >
  629. <span x-text="person.name"></span>
  630. </li>
  631. </template>
  632. </ul>
  633. </div>
  634. `],
  635. ({ get, wait }) => {
  636. get('.active').should(notExist())
  637. get('button').click()
  638. get('[options]')
  639. .type('ar')
  640. get('[option="2"]')
  641. .should(haveClasses(['active']))
  642. wait(500)
  643. get('[options]')
  644. .type('ma')
  645. get('[option="8"]')
  646. .should(haveClasses(['active']))
  647. },
  648. )
  649. test('changing value manually changes internal state',
  650. [html`
  651. <div
  652. x-data="{ active: null, people: [
  653. { id: 1, name: 'Wade Cooper' },
  654. { id: 2, name: 'Arlene Mccoy' },
  655. { id: 3, name: 'Devon Webb', disabled: true },
  656. { id: 4, name: 'Tom Cook' },
  657. { id: 5, name: 'Tanya Fox', disabled: true },
  658. { id: 6, name: 'Hellen Schmidt' },
  659. { id: 7, name: 'Caroline Schultz' },
  660. { id: 8, name: 'Mason Heaney' },
  661. { id: 9, name: 'Claudie Smitham' },
  662. { id: 10, name: 'Emil Schaefer' },
  663. ]}"
  664. x-listbox
  665. x-model="active"
  666. >
  667. <label x-listbox:label>Assigned to</label>
  668. <button toggle x-listbox:button x-text="$listbox.selected ? $listbox.selected : 'Select Person'"></button>
  669. <button select-tim @click="active = 4">Select Tim</button>
  670. <ul x-listbox:options options>
  671. <template x-for="person in people" :key="person.id">
  672. <li
  673. :option="person.id"
  674. x-listbox:option
  675. :value="person.id"
  676. :disabled="person.disabled"
  677. :class="{
  678. 'selected': $listboxOption.isSelected,
  679. 'active': $listboxOption.isActive,
  680. }"
  681. >
  682. <span x-text="person.name"></span>
  683. </li>
  684. </template>
  685. </ul>
  686. </div>
  687. `],
  688. ({ get }) => {
  689. get('[select-tim]').click()
  690. get('[option="4"]').should(haveClasses(['selected']))
  691. get('[option="1"]').should(notHaveClasses(['selected']))
  692. get('[toggle]').should(haveText('4'))
  693. },
  694. )
  695. test('has accessibility attributes',
  696. [html`
  697. <div
  698. x-data="{ active: null, people: [
  699. { id: 1, name: 'Wade Cooper' },
  700. { id: 2, name: 'Arlene Mccoy' },
  701. { id: 3, name: 'Devon Webb', disabled: true },
  702. { id: 4, name: 'Tom Cook' },
  703. { id: 5, name: 'Tanya Fox', disabled: true },
  704. { id: 6, name: 'Hellen Schmidt' },
  705. { id: 7, name: 'Caroline Schultz' },
  706. { id: 8, name: 'Mason Heaney' },
  707. { id: 9, name: 'Claudie Smitham' },
  708. { id: 10, name: 'Emil Schaefer' },
  709. ]}"
  710. x-listbox
  711. x-model="active"
  712. >
  713. <label x-listbox:label>Assigned to</label>
  714. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  715. <ul x-listbox:options options>
  716. <template x-for="person in people" :key="person.id">
  717. <li
  718. :option="person.id"
  719. x-listbox:option
  720. :value="person"
  721. :disabled="person.disabled"
  722. :class="{
  723. 'selected': $listboxOption.isSelected,
  724. 'active': $listboxOption.isActive,
  725. }"
  726. >
  727. <span x-text="person.name"></span>
  728. </li>
  729. </template>
  730. </ul>
  731. </div>
  732. `],
  733. ({ get }) => {
  734. get('button')
  735. .should(haveAttribute('aria-haspopup', 'true'))
  736. .should(haveAttribute('aria-labelledby', 'alpine-listbox-label-1'))
  737. .should(haveAttribute('aria-expanded', 'false'))
  738. .should(notHaveAttribute('aria-controls'))
  739. .should(haveAttribute('id', 'alpine-listbox-button-1'))
  740. .click()
  741. .should(haveAttribute('aria-expanded', 'true'))
  742. .should(haveAttribute('aria-controls', 'alpine-listbox-options-1'))
  743. get('[options]')
  744. .should(haveAttribute('aria-orientation', 'vertical'))
  745. .should(haveAttribute('role', 'listbox'))
  746. .should(haveAttribute('id', 'alpine-listbox-options-1'))
  747. .should(haveAttribute('aria-labelledby', 'alpine-listbox-button-1'))
  748. .should(notHaveAttribute('aria-activedescendant'))
  749. .should(haveAttribute('tabindex', '0'))
  750. .should(haveAttribute('aria-activedescendant', 'alpine-listbox-option-1'))
  751. get('[option="1"]')
  752. .should(haveAttribute('role', 'option'))
  753. .should(haveAttribute('id', 'alpine-listbox-option-1'))
  754. .should(haveAttribute('tabindex', '-1'))
  755. .should(haveAttribute('aria-selected', 'false'))
  756. get('[option="2"]')
  757. .should(haveAttribute('role', 'option'))
  758. .should(haveAttribute('id', 'alpine-listbox-option-2'))
  759. .should(haveAttribute('tabindex', '-1'))
  760. .should(haveAttribute('aria-selected', 'false'))
  761. get('[options]')
  762. .type('{downarrow}')
  763. .should(haveAttribute('aria-activedescendant', 'alpine-listbox-option-2'))
  764. get('[option="2"]')
  765. .click()
  766. .should(haveAttribute('aria-selected', 'true'))
  767. },
  768. )
  769. test('"static" prop',
  770. [html`
  771. <div
  772. x-data="{ active: null, show: false, people: [
  773. { id: 1, name: 'Wade Cooper' },
  774. { id: 2, name: 'Arlene Mccoy' },
  775. { id: 3, name: 'Devon Webb' },
  776. { id: 4, name: 'Tom Cook' },
  777. { id: 5, name: 'Tanya Fox', disabled: true },
  778. { id: 6, name: 'Hellen Schmidt' },
  779. { id: 7, name: 'Caroline Schultz' },
  780. { id: 8, name: 'Mason Heaney' },
  781. { id: 9, name: 'Claudie Smitham' },
  782. { id: 10, name: 'Emil Schaefer' },
  783. ]}"
  784. x-listbox
  785. x-model="active"
  786. >
  787. <label x-listbox:label>Assigned to</label>
  788. <button normal-toggle x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  789. <button real-toggle @click="show = ! show">Toggle</button>
  790. <ul x-listbox:options x-show="show" static>
  791. <template x-for="person in people" :key="person.id">
  792. <li
  793. :option="person.id"
  794. x-listbox:option
  795. :value="person"
  796. :disabled="person.disabled"
  797. >
  798. <span x-text="person.name"></span>
  799. </li>
  800. </template>
  801. </ul>
  802. </div>
  803. `],
  804. ({ get }) => {
  805. get('ul').should(notBeVisible())
  806. get('[normal-toggle]')
  807. .should(haveText('Select Person'))
  808. .click()
  809. get('ul').should(notBeVisible())
  810. get('[real-toggle]').click()
  811. get('ul').should(beVisible())
  812. get('[option="2"]').click()
  813. get('ul').should(beVisible())
  814. get('[normal-toggle]').should(haveText('Arlene Mccoy'))
  815. },
  816. )
  817. // test "by" attribute