listbox.spec.js 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. import { beVisible, beHidden, haveAttribute, haveClasses, notHaveClasses, haveText, html, notBeVisible, notHaveAttribute, notExist, haveFocus, test, ensureNoConsoleWarns} 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('"by" prop with string value',
  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. by="id"
  615. >
  616. <label x-listbox:label>Assigned to</label>
  617. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  618. <ul x-listbox:options options>
  619. <template x-for="person in people" :key="person.id">
  620. <li
  621. :option="person.id"
  622. x-listbox:option
  623. :value="person"
  624. :disabled="person.disabled"
  625. :class="{
  626. 'selected': $listboxOption.isSelected,
  627. 'active': $listboxOption.isActive,
  628. }"
  629. >
  630. <span x-text="person.name"></span>
  631. </li>
  632. </template>
  633. </ul>
  634. </div>
  635. `],
  636. ({ get }) => {
  637. get('ul').should(notBeVisible())
  638. get('button')
  639. .should(haveText('Select Person'))
  640. .click()
  641. get('ul').should(beVisible())
  642. get('button').click()
  643. get('ul').should(notBeVisible())
  644. get('button').click()
  645. get('[option="2"]').click()
  646. get('ul').should(notBeVisible())
  647. get('button').should(haveText('Arlene Mccoy'))
  648. },
  649. )
  650. test('search',
  651. [html`
  652. <div
  653. x-data="{ active: null, people: [
  654. { id: 1, name: 'Wade Cooper' },
  655. { id: 2, name: 'Arlene Mccoy' },
  656. { id: 3, name: 'Devon Webb', disabled: true },
  657. { id: 4, name: 'Tom Cook' },
  658. { id: 5, name: 'Tanya Fox', disabled: true },
  659. { id: 6, name: 'Hellen Schmidt' },
  660. { id: 7, name: 'Caroline Schultz' },
  661. { id: 8, name: 'Mason Heaney' },
  662. { id: 9, name: 'Claudie Smitham' },
  663. { id: 10, name: 'Emil Schaefer' },
  664. ]}"
  665. x-listbox
  666. x-model="active"
  667. >
  668. <label x-listbox:label>Assigned to</label>
  669. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></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"
  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, wait }) => {
  689. get('.active').should(notExist())
  690. get('button').click()
  691. get('[options]')
  692. .type('ar')
  693. get('[option="2"]')
  694. .should(haveClasses(['active']))
  695. wait(500)
  696. get('[options]')
  697. .type('ma')
  698. get('[option="8"]')
  699. .should(haveClasses(['active']))
  700. },
  701. )
  702. test('changing value manually changes internal state',
  703. [html`
  704. <div
  705. x-data="{ active: null, people: [
  706. { id: 1, name: 'Wade Cooper' },
  707. { id: 2, name: 'Arlene Mccoy' },
  708. { id: 3, name: 'Devon Webb', disabled: true },
  709. { id: 4, name: 'Tom Cook' },
  710. { id: 5, name: 'Tanya Fox', disabled: true },
  711. { id: 6, name: 'Hellen Schmidt' },
  712. { id: 7, name: 'Caroline Schultz' },
  713. { id: 8, name: 'Mason Heaney' },
  714. { id: 9, name: 'Claudie Smitham' },
  715. { id: 10, name: 'Emil Schaefer' },
  716. ]}"
  717. x-listbox
  718. x-model="active"
  719. >
  720. <label x-listbox:label>Assigned to</label>
  721. <button toggle x-listbox:button x-text="$listbox.selected ? $listbox.selected : 'Select Person'"></button>
  722. <button select-tim @click="active = 4">Select Tim</button>
  723. <ul x-listbox:options options>
  724. <template x-for="person in people" :key="person.id">
  725. <li
  726. :option="person.id"
  727. x-listbox:option
  728. :value="person.id"
  729. :disabled="person.disabled"
  730. :class="{
  731. 'selected': $listboxOption.isSelected,
  732. 'active': $listboxOption.isActive,
  733. }"
  734. >
  735. <span x-text="person.name"></span>
  736. </li>
  737. </template>
  738. </ul>
  739. </div>
  740. `],
  741. ({ get }) => {
  742. get('[select-tim]').click()
  743. get('[option="4"]').should(haveClasses(['selected']))
  744. get('[option="1"]').should(notHaveClasses(['selected']))
  745. get('[toggle]').should(haveText('4'))
  746. },
  747. )
  748. test('has accessibility attributes',
  749. [html`
  750. <div
  751. x-data="{ active: null, people: [
  752. { id: 1, name: 'Wade Cooper' },
  753. { id: 2, name: 'Arlene Mccoy' },
  754. { id: 3, name: 'Devon Webb', disabled: true },
  755. { id: 4, name: 'Tom Cook' },
  756. { id: 5, name: 'Tanya Fox', disabled: true },
  757. { id: 6, name: 'Hellen Schmidt' },
  758. { id: 7, name: 'Caroline Schultz' },
  759. { id: 8, name: 'Mason Heaney' },
  760. { id: 9, name: 'Claudie Smitham' },
  761. { id: 10, name: 'Emil Schaefer' },
  762. ]}"
  763. x-listbox
  764. x-model="active"
  765. >
  766. <label x-listbox:label>Assigned to</label>
  767. <button x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  768. <ul x-listbox:options options>
  769. <template x-for="person in people" :key="person.id">
  770. <li
  771. :option="person.id"
  772. x-listbox:option
  773. :value="person"
  774. :disabled="person.disabled"
  775. :class="{
  776. 'selected': $listboxOption.isSelected,
  777. 'active': $listboxOption.isActive,
  778. }"
  779. >
  780. <span x-text="person.name"></span>
  781. </li>
  782. </template>
  783. </ul>
  784. </div>
  785. `],
  786. ({ get }) => {
  787. get('button')
  788. .should(haveAttribute('aria-haspopup', 'true'))
  789. .should(haveAttribute('aria-labelledby', 'alpine-listbox-label-1'))
  790. .should(haveAttribute('aria-expanded', 'false'))
  791. .should(notHaveAttribute('aria-controls'))
  792. .should(haveAttribute('id', 'alpine-listbox-button-1'))
  793. .click()
  794. .should(haveAttribute('aria-expanded', 'true'))
  795. .should(haveAttribute('aria-controls', 'alpine-listbox-options-1'))
  796. get('[options]')
  797. .should(haveAttribute('aria-orientation', 'vertical'))
  798. .should(haveAttribute('role', 'listbox'))
  799. .should(haveAttribute('id', 'alpine-listbox-options-1'))
  800. .should(haveAttribute('aria-labelledby', 'alpine-listbox-button-1'))
  801. .should(notHaveAttribute('aria-activedescendant'))
  802. .should(haveAttribute('tabindex', '0'))
  803. .should(haveAttribute('aria-activedescendant', 'alpine-listbox-option-1'))
  804. get('[option="1"]')
  805. .should(haveAttribute('role', 'option'))
  806. .should(haveAttribute('id', 'alpine-listbox-option-1'))
  807. .should(haveAttribute('tabindex', '-1'))
  808. .should(haveAttribute('aria-selected', 'false'))
  809. get('[option="2"]')
  810. .should(haveAttribute('role', 'option'))
  811. .should(haveAttribute('id', 'alpine-listbox-option-2'))
  812. .should(haveAttribute('tabindex', '-1'))
  813. .should(haveAttribute('aria-selected', 'false'))
  814. get('[options]')
  815. .type('{downarrow}')
  816. .should(haveAttribute('aria-activedescendant', 'alpine-listbox-option-2'))
  817. get('[option="2"]')
  818. .click()
  819. .should(haveAttribute('aria-selected', 'true'))
  820. },
  821. )
  822. test('"static" prop',
  823. [html`
  824. <div
  825. x-data="{ active: null, show: false, people: [
  826. { id: 1, name: 'Wade Cooper' },
  827. { id: 2, name: 'Arlene Mccoy' },
  828. { id: 3, name: 'Devon Webb' },
  829. { id: 4, name: 'Tom Cook' },
  830. { id: 5, name: 'Tanya Fox', disabled: true },
  831. { id: 6, name: 'Hellen Schmidt' },
  832. { id: 7, name: 'Caroline Schultz' },
  833. { id: 8, name: 'Mason Heaney' },
  834. { id: 9, name: 'Claudie Smitham' },
  835. { id: 10, name: 'Emil Schaefer' },
  836. ]}"
  837. x-listbox
  838. x-model="active"
  839. >
  840. <label x-listbox:label>Assigned to</label>
  841. <button normal-toggle x-listbox:button x-text="active ? active.name : 'Select Person'"></button>
  842. <button real-toggle @click="show = ! show">Toggle</button>
  843. <ul x-listbox:options x-show="show" static>
  844. <template x-for="person in people" :key="person.id">
  845. <li
  846. :option="person.id"
  847. x-listbox:option
  848. :value="person"
  849. :disabled="person.disabled"
  850. >
  851. <span x-text="person.name"></span>
  852. </li>
  853. </template>
  854. </ul>
  855. </div>
  856. `],
  857. ({ get }) => {
  858. get('ul').should(notBeVisible())
  859. get('[normal-toggle]')
  860. .should(haveText('Select Person'))
  861. .click()
  862. get('ul').should(notBeVisible())
  863. get('[real-toggle]').click()
  864. get('ul').should(beVisible())
  865. get('[option="2"]').click()
  866. get('ul').should(beVisible())
  867. get('[normal-toggle]').should(haveText('Arlene Mccoy'))
  868. },
  869. )
  870. test('works with morph',
  871. [html`
  872. <div x-data="{ value: null }">
  873. <div x-listbox x-model="value">
  874. <button x-listbox:button>Select Framework</button>
  875. <ul x-listbox:options>
  876. <li x-listbox:option value="laravel">Laravel</li>
  877. </ul>
  878. </div>
  879. Selected: <span x-text="value"></span>
  880. </div>
  881. `],
  882. ({ get }, reload, window, document) => {
  883. let toHtml = html`
  884. <div x-data="{ value: null }">
  885. <div x-listbox x-model="value">
  886. <button x-listbox:button>Select Framework (updated)</button>
  887. <ul x-listbox:options>
  888. <li x-listbox:option value="laravel">Laravel</li>
  889. </ul>
  890. </div>
  891. Selected: <span x-text="value"></span>
  892. </div>
  893. `
  894. ensureNoConsoleWarns()
  895. get('div').then(([el]) => window.Alpine.morph(el, toHtml))
  896. get('button').should(haveText('Select Framework (updated)'))
  897. },
  898. )
  899. test('boolean option values',
  900. [html`
  901. <div x-data="{ value: null }" x-listbox x-model="value">
  902. <label x-listbox:label>Value</label>
  903. <button x-listbox:button x-text="value !== null ? value.toString() : 'Select boolean'"></button>
  904. <ul x-listbox:options options>
  905. <li
  906. option="boolean-true"
  907. x-listbox:option
  908. :value="true"
  909. :class="{
  910. 'selected': $listboxOption.isSelected,
  911. 'active': $listboxOption.isActive,
  912. }">
  913. <span>Yes</span>
  914. </li>
  915. <li
  916. option="boolean-false"
  917. x-listbox:option
  918. :value="false"
  919. :class="{
  920. 'selected': $listboxOption.isSelected,
  921. 'active': $listboxOption.isActive,
  922. }">
  923. <span>No</span>
  924. </li>
  925. </ul>
  926. </div>
  927. `],
  928. ({ get }) => {
  929. get('ul').should(notBeVisible())
  930. get('button')
  931. .should(haveText('Select boolean'))
  932. .click()
  933. get('ul').should(beVisible())
  934. get('[option="boolean-true"]').should(notHaveClasses(['selected']))
  935. get('[option="boolean-false"]').should(notHaveClasses(['selected']))
  936. get('[option="boolean-true"]').click()
  937. get('ul').should(notBeVisible())
  938. get('button').should(haveText('true'))
  939. get('button').click()
  940. get('ul').should(beVisible())
  941. get('[option="boolean-true"]').should(haveClasses(['selected']))
  942. get('[option="boolean-false"]').should(notHaveClasses(['selected']))
  943. get('[option="boolean-false"]').click()
  944. get('ul').should(notBeVisible())
  945. get('button').should(haveText('false'))
  946. get('button').click()
  947. get('ul').should(beVisible())
  948. get('[option="boolean-true"]').should(notHaveClasses(['selected']))
  949. get('[option="boolean-false"]').should(haveClasses(['selected']))
  950. },
  951. )
  952. test('integer option values',
  953. [html`
  954. <div x-data="{ value: null }" x-listbox x-model="value">
  955. <label x-listbox:label>Value</label>
  956. <button x-listbox:button x-text="value !== null ? value.toString() : 'Select number'"></button>
  957. <ul x-listbox:options options>
  958. <li
  959. option="0"
  960. x-listbox:option
  961. :value="0"
  962. :class="{
  963. 'selected': $listboxOption.isSelected,
  964. 'active': $listboxOption.isActive,
  965. }">
  966. <span>0</span>
  967. </li>
  968. <li
  969. option="1"
  970. x-listbox:option
  971. :value="1"
  972. :class="{
  973. 'selected': $listboxOption.isSelected,
  974. 'active': $listboxOption.isActive,
  975. }">
  976. <span>1</span>
  977. </li>
  978. <li
  979. option="2"
  980. x-listbox:option
  981. :value="2"
  982. :class="{
  983. 'selected': $listboxOption.isSelected,
  984. 'active': $listboxOption.isActive,
  985. }">
  986. <span>2</span>
  987. </li>
  988. </ul>
  989. </div>
  990. `],
  991. ({ get }) => {
  992. get('ul').should(notBeVisible())
  993. get('button')
  994. .should(haveText('Select number'))
  995. .click()
  996. get('ul').should(beVisible())
  997. get('[option="0"]').should(notHaveClasses(['selected']))
  998. get('[option="1"]').should(notHaveClasses(['selected']))
  999. get('[option="2"]').should(notHaveClasses(['selected']))
  1000. get('[option="1"]').click()
  1001. get('ul').should(notBeVisible())
  1002. get('button').should(haveText('1'))
  1003. get('button').click()
  1004. get('ul').should(beVisible())
  1005. get('[option="0"]').should(notHaveClasses(['selected']))
  1006. get('[option="1"]').should(haveClasses(['selected']))
  1007. get('[option="2"]').should(notHaveClasses(['selected']))
  1008. get('[option="0"]').click()
  1009. get('ul').should(notBeVisible())
  1010. get('button').should(haveText('0'))
  1011. get('button').click()
  1012. get('ul').should(beVisible())
  1013. get('[option="0"]').should(haveClasses(['selected']))
  1014. get('[option="1"]').should(notHaveClasses(['selected']))
  1015. get('[option="2"]').should(notHaveClasses(['selected']))
  1016. get('[option="2"]').click()
  1017. get('ul').should(notBeVisible())
  1018. get('button').should(haveText('2'))
  1019. get('button').click()
  1020. get('ul').should(beVisible())
  1021. get('[option="0"]').should(notHaveClasses(['selected']))
  1022. get('[option="1"]').should(notHaveClasses(['selected']))
  1023. get('[option="2"]').should(haveClasses(['selected']))
  1024. },
  1025. )
  1026. // test "by" attribute