menu.spec.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { haveClasses, beVisible, haveAttribute, haveText, html, notBeVisible, notExist, test, haveFocus, notHaveClasses, notHaveAttribute } from '../../../utils'
  2. test('it works',
  3. [html`
  4. <div x-data x-menu>
  5. <span>
  6. <button x-menu:button trigger>
  7. <span>Options</span>
  8. </button>
  9. </span>
  10. <div x-menu:items items>
  11. <div>
  12. <p>Signed in as</p>
  13. <p>tom@example.com</p>
  14. </div>
  15. <div>
  16. <a x-menu:item href="#account-settings">
  17. Account settings
  18. </a>
  19. <a x-menu:item href="#support">
  20. Support
  21. </a>
  22. <a x-menu:item disabled href="#new-feature">
  23. New feature (soon)
  24. </a>
  25. <a x-menu:item href="#license">
  26. License
  27. </a>
  28. </div>
  29. <div>
  30. <a x-menu:item href="#sign-out">
  31. Sign out
  32. </a>
  33. </div>
  34. </div>
  35. </div>`],
  36. ({ get }) => {
  37. get('[items]').should(notBeVisible())
  38. get('[trigger]').click()
  39. get('[items]').should(beVisible())
  40. },
  41. )
  42. test('keyboard controls',
  43. [html`
  44. <div x-data x-menu>
  45. <span>
  46. <button x-menu:button trigger>
  47. <span>Options</span>
  48. </button>
  49. </span>
  50. <div x-menu:items items>
  51. <div>
  52. <p>Signed in as</p>
  53. <p>tom@example.com</p>
  54. </div>
  55. <div>
  56. <a x-menu:item href="#account-settings" :class="$menuItem.isActive && 'active'">
  57. Account settings
  58. </a>
  59. <a x-menu:item href="#support" :class="$menuItem.isActive && 'active'">
  60. Support
  61. </a>
  62. <a x-menu:item disabled href="#new-feature" :class="$menuItem.isActive && 'active'">
  63. New feature (soon)
  64. </a>
  65. <a x-menu:item href="#license" :class="$menuItem.isActive && 'active'">
  66. License
  67. </a>
  68. </div>
  69. <div>
  70. <a x-menu:item href="#sign-out" :class="$menuItem.isActive && 'active'">
  71. Sign out
  72. </a>
  73. </div>
  74. </div>
  75. </div>`],
  76. ({ get }) => {
  77. get('.active').should(notExist())
  78. get('[trigger]').type(' ')
  79. get('[items]')
  80. .should(beVisible())
  81. .should(haveFocus())
  82. .type('{downarrow}')
  83. get('[href="#account-settings"]')
  84. .should(haveClasses(['active']))
  85. get('[items]')
  86. .type('{downarrow}')
  87. get('[href="#support"]')
  88. .should(haveClasses(['active']))
  89. .type('{downarrow}')
  90. get('[href="#license"]')
  91. .should(haveClasses(['active']))
  92. get('[items]')
  93. .type('{uparrow}')
  94. get('[href="#support"]')
  95. .should(haveClasses(['active']))
  96. get('[items]')
  97. .type('{home}')
  98. get('[href="#account-settings"]')
  99. .should(haveClasses(['active']))
  100. get('[items]')
  101. .type('{end}')
  102. get('[href="#sign-out"]')
  103. .should(haveClasses(['active']))
  104. get('[items]')
  105. .type('{pageUp}')
  106. get('[href="#account-settings"]')
  107. .should(haveClasses(['active']))
  108. get('[items]')
  109. .type('{pageDown}')
  110. get('[href="#sign-out"]')
  111. .should(haveClasses(['active']))
  112. get('[items]')
  113. .tab()
  114. .should(haveFocus())
  115. .should(beVisible())
  116. .tab({ shift: true})
  117. .should(haveFocus())
  118. .should(beVisible())
  119. .type('{esc}')
  120. .should(notBeVisible())
  121. },
  122. )
  123. test('has accessibility attributes',
  124. [html`
  125. <div x-data x-menu>
  126. <label x-menu:label>Options label</label>
  127. <span>
  128. <button x-menu:button trigger>
  129. <span>Options</span>
  130. </button>
  131. </span>
  132. <div x-menu:items items>
  133. <div>
  134. <p>Signed in as</p>
  135. <p>tom@example.com</p>
  136. </div>
  137. <div>
  138. <a x-menu:item href="#account-settings" :class="$menuItem.isActive && 'active'">
  139. Account settings
  140. </a>
  141. <a x-menu:item href="#support" :class="$menuItem.isActive && 'active'">
  142. Support
  143. </a>
  144. <a x-menu:item disabled href="#new-feature" :class="$menuItem.isActive && 'active'">
  145. New feature (soon)
  146. </a>
  147. <a x-menu:item href="#license" :class="$menuItem.isActive && 'active'">
  148. License
  149. </a>
  150. </div>
  151. <div>
  152. <a x-menu:item href="#sign-out" :class="$menuItem.isActive && 'active'">
  153. Sign out
  154. </a>
  155. </div>
  156. </div>
  157. </div>`],
  158. ({ get }) => {
  159. get('[trigger]')
  160. .should(haveAttribute('aria-haspopup', 'true'))
  161. .should(haveAttribute('aria-labelledby', 'alpine-menu-label-1'))
  162. .should(haveAttribute('aria-expanded', 'false'))
  163. .should(notHaveAttribute('aria-controls'))
  164. .should(haveAttribute('id', 'alpine-menu-button-1'))
  165. .click()
  166. .should(haveAttribute('aria-expanded', 'true'))
  167. .should(haveAttribute('aria-controls', 'alpine-menu-items-1'))
  168. get('[items]')
  169. .should(haveAttribute('aria-orientation', 'vertical'))
  170. .should(haveAttribute('role', 'menu'))
  171. .should(haveAttribute('id', 'alpine-menu-items-1'))
  172. .should(haveAttribute('aria-labelledby', 'alpine-menu-button-1'))
  173. .should(notHaveAttribute('aria-activedescendant'))
  174. .should(haveAttribute('tabindex', '0'))
  175. .type('{downarrow}')
  176. .should(haveAttribute('aria-activedescendant', 'alpine-menu-item-1'))
  177. get('[href="#account-settings"]')
  178. .should(haveAttribute('role', 'menuitem'))
  179. .should(haveAttribute('id', 'alpine-menu-item-1'))
  180. .should(haveAttribute('tabindex', '-1'))
  181. get('[href="#support"]')
  182. .should(haveAttribute('role', 'menuitem'))
  183. .should(haveAttribute('id', 'alpine-menu-item-2'))
  184. .should(haveAttribute('tabindex', '-1'))
  185. get('[items]')
  186. .type('{downarrow}')
  187. .should(haveAttribute('aria-activedescendant', 'alpine-menu-item-2'))
  188. },
  189. )
  190. test('$menuItem.isDisabled',
  191. [html`
  192. <div x-data x-menu>
  193. <label x-menu:label>Options label</label>
  194. <span>
  195. <button x-menu:button trigger>
  196. <span>Options</span>
  197. </button>
  198. </span>
  199. <div x-menu:items items>
  200. <div>
  201. <p>Signed in as</p>
  202. <p>tom@example.com</p>
  203. </div>
  204. <div>
  205. <a x-menu:item href="#account-settings" :class="{ 'active': $menuItem.isActive, 'disabled': $menuItem.isDisabled }">
  206. Account settings
  207. </a>
  208. <a x-menu:item href="#support" :class="{ 'active': $menuItem.isActive, 'disabled': $menuItem.isDisabled }">
  209. Support
  210. </a>
  211. <a x-menu:item disabled href="#new-feature" :class="{ 'active': $menuItem.isActive, 'disabled': $menuItem.isDisabled }">
  212. New feature (soon)
  213. </a>
  214. <a x-menu:item href="#license" :class="{ 'active': $menuItem.isActive, 'disabled': $menuItem.isDisabled }">
  215. License
  216. </a>
  217. </div>
  218. <div>
  219. <a x-menu:item href="#sign-out" :class="{ 'active': $menuItem.isActive, 'disabled': $menuItem.isDisabled }">
  220. Sign out
  221. </a>
  222. </div>
  223. </div>
  224. </div>`],
  225. ({ get }) => {
  226. get('[trigger]').click()
  227. get('[href="#account-settings"]').should(notHaveClasses(['disabled']))
  228. get('[href="#support"]').should(notHaveClasses(['disabled']))
  229. get('[href="#new-feature"]').should(haveClasses(['disabled']))
  230. },
  231. )
  232. // @todo: add keydown search and debounced search tests