shadcn-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +40 -0
  3. data/CHANGELOG.md +54 -0
  4. data/CLAUDE.md +463 -0
  5. data/PROGRESS.md +485 -0
  6. data/README.md +1483 -0
  7. data/Rakefile +29 -0
  8. data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +13 -0
  9. data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +46 -0
  10. data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +111 -0
  11. data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +27 -0
  12. data/__tests__/controllers/accordion_controller.test.js +904 -0
  13. data/__tests__/controllers/calendar_controller.test.js +1370 -0
  14. data/__tests__/controllers/carousel_controller.test.js +912 -0
  15. data/__tests__/controllers/checkbox_controller.test.js +454 -0
  16. data/__tests__/controllers/collapsible_controller.test.js +407 -0
  17. data/__tests__/controllers/combobox_controller.test.js +966 -0
  18. data/__tests__/controllers/context_menu_controller.test.js +627 -0
  19. data/__tests__/controllers/date_picker_controller.test.js +636 -0
  20. data/__tests__/controllers/dialog_controller.test.js +878 -0
  21. data/__tests__/controllers/drawer_controller.test.js +995 -0
  22. data/__tests__/controllers/menubar_controller.test.js +736 -0
  23. data/__tests__/controllers/navigation_menu_controller.test.js +598 -0
  24. data/__tests__/controllers/popover_controller.test.js +1007 -0
  25. data/__tests__/controllers/radio_group_controller.test.js +640 -0
  26. data/__tests__/controllers/resizable_controller.test.js +680 -0
  27. data/__tests__/controllers/select_controller.test.js +674 -0
  28. data/__tests__/controllers/sheet_controller.test.js +986 -0
  29. data/__tests__/controllers/slider_controller.test.js +1036 -0
  30. data/__tests__/controllers/switch_controller.test.js +424 -0
  31. data/__tests__/controllers/tabs_controller.test.js +907 -0
  32. data/__tests__/controllers/toggle_group_controller.test.js +839 -0
  33. data/__tests__/controllers/tooltip_controller.test.js +808 -0
  34. data/__tests__/helpers/stimulus-test-helper.js +203 -0
  35. data/app/assets/config/manifest.js +1 -0
  36. data/app/assets/javascripts/shadcn/controllers/accordion_controller.d.ts +53 -0
  37. data/app/assets/javascripts/shadcn/controllers/accordion_controller.js +140 -0
  38. data/app/assets/javascripts/shadcn/controllers/avatar_controller.d.ts +22 -0
  39. data/app/assets/javascripts/shadcn/controllers/avatar_controller.js +26 -0
  40. data/app/assets/javascripts/shadcn/controllers/calendar_controller.js +592 -0
  41. data/app/assets/javascripts/shadcn/controllers/carousel_controller.js +263 -0
  42. data/app/assets/javascripts/shadcn/controllers/checkbox_controller.d.ts +31 -0
  43. data/app/assets/javascripts/shadcn/controllers/checkbox_controller.js +48 -0
  44. data/app/assets/javascripts/shadcn/controllers/collapsible_controller.d.ts +43 -0
  45. data/app/assets/javascripts/shadcn/controllers/collapsible_controller.js +73 -0
  46. data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +234 -0
  47. data/app/assets/javascripts/shadcn/controllers/command_controller.js +141 -0
  48. data/app/assets/javascripts/shadcn/controllers/command_dialog_controller.js +162 -0
  49. data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +202 -0
  50. data/app/assets/javascripts/shadcn/controllers/date_picker_controller.js +282 -0
  51. data/app/assets/javascripts/shadcn/controllers/dialog_controller.d.ts +67 -0
  52. data/app/assets/javascripts/shadcn/controllers/dialog_controller.js +187 -0
  53. data/app/assets/javascripts/shadcn/controllers/drawer_controller.d.ts +58 -0
  54. data/app/assets/javascripts/shadcn/controllers/drawer_controller.js +112 -0
  55. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.d.ts +83 -0
  56. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +225 -0
  57. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.d.ts +59 -0
  58. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +143 -0
  59. data/app/assets/javascripts/shadcn/controllers/input_otp_controller.d.ts +44 -0
  60. data/app/assets/javascripts/shadcn/controllers/input_otp_controller.js +206 -0
  61. data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +323 -0
  62. data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +251 -0
  63. data/app/assets/javascripts/shadcn/controllers/popover_controller.d.ts +56 -0
  64. data/app/assets/javascripts/shadcn/controllers/popover_controller.js +141 -0
  65. data/app/assets/javascripts/shadcn/controllers/radio_group_controller.d.ts +47 -0
  66. data/app/assets/javascripts/shadcn/controllers/radio_group_controller.js +108 -0
  67. data/app/assets/javascripts/shadcn/controllers/resizable_controller.js +272 -0
  68. data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.d.ts +44 -0
  69. data/app/assets/javascripts/shadcn/controllers/scroll_area_controller.js +74 -0
  70. data/app/assets/javascripts/shadcn/controllers/select_controller.d.ts +84 -0
  71. data/app/assets/javascripts/shadcn/controllers/select_controller.js +222 -0
  72. data/app/assets/javascripts/shadcn/controllers/sheet_controller.d.ts +60 -0
  73. data/app/assets/javascripts/shadcn/controllers/sheet_controller.js +151 -0
  74. data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +148 -0
  75. data/app/assets/javascripts/shadcn/controllers/slider_controller.d.ts +102 -0
  76. data/app/assets/javascripts/shadcn/controllers/slider_controller.js +364 -0
  77. data/app/assets/javascripts/shadcn/controllers/switch_controller.d.ts +46 -0
  78. data/app/assets/javascripts/shadcn/controllers/switch_controller.js +78 -0
  79. data/app/assets/javascripts/shadcn/controllers/tabs_controller.d.ts +51 -0
  80. data/app/assets/javascripts/shadcn/controllers/tabs_controller.js +126 -0
  81. data/app/assets/javascripts/shadcn/controllers/toast_controller.d.ts +37 -0
  82. data/app/assets/javascripts/shadcn/controllers/toast_controller.js +58 -0
  83. data/app/assets/javascripts/shadcn/controllers/toggle_controller.d.ts +27 -0
  84. data/app/assets/javascripts/shadcn/controllers/toggle_controller.js +42 -0
  85. data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.d.ts +44 -0
  86. data/app/assets/javascripts/shadcn/controllers/toggle_group_controller.js +68 -0
  87. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.d.ts +56 -0
  88. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +117 -0
  89. data/app/assets/javascripts/shadcn/index.d.ts +74 -0
  90. data/app/assets/javascripts/shadcn/index.js +133 -0
  91. data/app/assets/stylesheets/.keep +0 -0
  92. data/app/assets/stylesheets/shadcn/base.css +445 -0
  93. data/app/assets/stylesheets/shadcn/components.css +513 -0
  94. data/app/assets/stylesheets/shadcn/index.css +18 -0
  95. data/app/assets/stylesheets/shadcn/themes/gray.css +68 -0
  96. data/app/assets/stylesheets/shadcn/themes/slate.css +68 -0
  97. data/app/assets/stylesheets/shadcn/themes/stone.css +68 -0
  98. data/app/assets/stylesheets/shadcn/themes/zinc.css +68 -0
  99. data/app/components/shadcn/accordion_component.rb +63 -0
  100. data/app/components/shadcn/accordion_content_component.rb +29 -0
  101. data/app/components/shadcn/accordion_item_component.rb +40 -0
  102. data/app/components/shadcn/accordion_trigger_component.rb +49 -0
  103. data/app/components/shadcn/alert_component.rb +75 -0
  104. data/app/components/shadcn/alert_description_component.rb +12 -0
  105. data/app/components/shadcn/alert_dialog_action_component.rb +24 -0
  106. data/app/components/shadcn/alert_dialog_cancel_component.rb +24 -0
  107. data/app/components/shadcn/alert_dialog_component.rb +71 -0
  108. data/app/components/shadcn/alert_dialog_content_component.rb +57 -0
  109. data/app/components/shadcn/alert_dialog_description_component.rb +12 -0
  110. data/app/components/shadcn/alert_dialog_footer_component.rb +19 -0
  111. data/app/components/shadcn/alert_dialog_header_component.rb +19 -0
  112. data/app/components/shadcn/alert_dialog_title_component.rb +12 -0
  113. data/app/components/shadcn/alert_title_component.rb +12 -0
  114. data/app/components/shadcn/aspect_ratio_component.rb +49 -0
  115. data/app/components/shadcn/avatar_component.rb +107 -0
  116. data/app/components/shadcn/avatar_fallback_component.rb +17 -0
  117. data/app/components/shadcn/badge_component.rb +49 -0
  118. data/app/components/shadcn/base_component.rb +100 -0
  119. data/app/components/shadcn/breadcrumb_component.rb +70 -0
  120. data/app/components/shadcn/breadcrumb_item_component.rb +50 -0
  121. data/app/components/shadcn/button_component.rb +141 -0
  122. data/app/components/shadcn/button_group_component.rb +69 -0
  123. data/app/components/shadcn/calendar_component.rb +337 -0
  124. data/app/components/shadcn/card_action_component.rb +10 -0
  125. data/app/components/shadcn/card_component.rb +63 -0
  126. data/app/components/shadcn/card_content_component.rb +19 -0
  127. data/app/components/shadcn/card_description_component.rb +12 -0
  128. data/app/components/shadcn/card_footer_component.rb +12 -0
  129. data/app/components/shadcn/card_header_component.rb +24 -0
  130. data/app/components/shadcn/card_title_component.rb +18 -0
  131. data/app/components/shadcn/carousel_component.rb +275 -0
  132. data/app/components/shadcn/checkbox_component.rb +103 -0
  133. data/app/components/shadcn/collapsible_component.rb +66 -0
  134. data/app/components/shadcn/collapsible_content_component.rb +28 -0
  135. data/app/components/shadcn/combobox_component.rb +322 -0
  136. data/app/components/shadcn/command_component.rb +52 -0
  137. data/app/components/shadcn/command_dialog_component.rb +76 -0
  138. data/app/components/shadcn/command_empty_component.rb +12 -0
  139. data/app/components/shadcn/command_group_component.rb +34 -0
  140. data/app/components/shadcn/command_input_component.rb +59 -0
  141. data/app/components/shadcn/command_item_component.rb +48 -0
  142. data/app/components/shadcn/command_list_component.rb +38 -0
  143. data/app/components/shadcn/command_separator_component.rb +12 -0
  144. data/app/components/shadcn/command_shortcut_component.rb +12 -0
  145. data/app/components/shadcn/context_menu_component.rb +64 -0
  146. data/app/components/shadcn/context_menu_content_component.rb +44 -0
  147. data/app/components/shadcn/context_menu_item_component.rb +63 -0
  148. data/app/components/shadcn/context_menu_label_component.rb +18 -0
  149. data/app/components/shadcn/context_menu_separator_component.rb +12 -0
  150. data/app/components/shadcn/context_menu_shortcut_component.rb +12 -0
  151. data/app/components/shadcn/date_picker_component.rb +368 -0
  152. data/app/components/shadcn/dialog_component.rb +77 -0
  153. data/app/components/shadcn/dialog_content_component.rb +91 -0
  154. data/app/components/shadcn/dialog_description_component.rb +12 -0
  155. data/app/components/shadcn/dialog_footer_component.rb +12 -0
  156. data/app/components/shadcn/dialog_header_component.rb +19 -0
  157. data/app/components/shadcn/dialog_title_component.rb +12 -0
  158. data/app/components/shadcn/drawer_component.rb +72 -0
  159. data/app/components/shadcn/drawer_content_component.rb +76 -0
  160. data/app/components/shadcn/drawer_description_component.rb +12 -0
  161. data/app/components/shadcn/drawer_footer_component.rb +12 -0
  162. data/app/components/shadcn/drawer_header_component.rb +19 -0
  163. data/app/components/shadcn/drawer_title_component.rb +12 -0
  164. data/app/components/shadcn/dropdown_menu_component.rb +75 -0
  165. data/app/components/shadcn/dropdown_menu_content_component.rb +49 -0
  166. data/app/components/shadcn/dropdown_menu_group_component.rb +10 -0
  167. data/app/components/shadcn/dropdown_menu_item_component.rb +63 -0
  168. data/app/components/shadcn/dropdown_menu_label_component.rb +18 -0
  169. data/app/components/shadcn/dropdown_menu_separator_component.rb +12 -0
  170. data/app/components/shadcn/dropdown_menu_shortcut_component.rb +12 -0
  171. data/app/components/shadcn/empty_component.rb +48 -0
  172. data/app/components/shadcn/empty_content_component.rb +12 -0
  173. data/app/components/shadcn/empty_description_component.rb +12 -0
  174. data/app/components/shadcn/empty_header_component.rb +29 -0
  175. data/app/components/shadcn/empty_media_component.rb +21 -0
  176. data/app/components/shadcn/empty_title_component.rb +12 -0
  177. data/app/components/shadcn/field_component.rb +113 -0
  178. data/app/components/shadcn/hover_card_component.rb +64 -0
  179. data/app/components/shadcn/hover_card_content_component.rb +36 -0
  180. data/app/components/shadcn/input_component.rb +108 -0
  181. data/app/components/shadcn/input_group_component.rb +70 -0
  182. data/app/components/shadcn/input_otp_component.rb +183 -0
  183. data/app/components/shadcn/item_actions_component.rb +12 -0
  184. data/app/components/shadcn/item_component.rb +98 -0
  185. data/app/components/shadcn/item_content_component.rb +24 -0
  186. data/app/components/shadcn/item_description_component.rb +12 -0
  187. data/app/components/shadcn/item_footer_component.rb +12 -0
  188. data/app/components/shadcn/item_group_component.rb +24 -0
  189. data/app/components/shadcn/item_header_component.rb +12 -0
  190. data/app/components/shadcn/item_media_component.rb +22 -0
  191. data/app/components/shadcn/item_separator_component.rb +12 -0
  192. data/app/components/shadcn/item_title_component.rb +12 -0
  193. data/app/components/shadcn/kbd_component.rb +36 -0
  194. data/app/components/shadcn/label_component.rb +49 -0
  195. data/app/components/shadcn/menubar_checkbox_item_component.rb +76 -0
  196. data/app/components/shadcn/menubar_component.rb +56 -0
  197. data/app/components/shadcn/menubar_content_component.rb +64 -0
  198. data/app/components/shadcn/menubar_item_component.rb +65 -0
  199. data/app/components/shadcn/menubar_label_component.rb +27 -0
  200. data/app/components/shadcn/menubar_menu_component.rb +34 -0
  201. data/app/components/shadcn/menubar_radio_group_component.rb +42 -0
  202. data/app/components/shadcn/menubar_radio_item_component.rb +76 -0
  203. data/app/components/shadcn/menubar_separator_component.rb +22 -0
  204. data/app/components/shadcn/menubar_shortcut_component.rb +21 -0
  205. data/app/components/shadcn/menubar_sub_component.rb +38 -0
  206. data/app/components/shadcn/menubar_sub_content_component.rb +45 -0
  207. data/app/components/shadcn/menubar_sub_trigger_component.rb +59 -0
  208. data/app/components/shadcn/menubar_trigger_component.rb +31 -0
  209. data/app/components/shadcn/native_select_component.rb +150 -0
  210. data/app/components/shadcn/navigation_menu_component.rb +76 -0
  211. data/app/components/shadcn/navigation_menu_content_component.rb +30 -0
  212. data/app/components/shadcn/navigation_menu_item_component.rb +39 -0
  213. data/app/components/shadcn/navigation_menu_link_component.rb +38 -0
  214. data/app/components/shadcn/navigation_menu_list_component.rb +29 -0
  215. data/app/components/shadcn/navigation_menu_trigger_component.rb +59 -0
  216. data/app/components/shadcn/pagination_component.rb +195 -0
  217. data/app/components/shadcn/pagination_content_component.rb +47 -0
  218. data/app/components/shadcn/pagination_ellipsis_component.rb +30 -0
  219. data/app/components/shadcn/pagination_item_component.rb +53 -0
  220. data/app/components/shadcn/pagination_next_component.rb +48 -0
  221. data/app/components/shadcn/pagination_previous_component.rb +48 -0
  222. data/app/components/shadcn/popover_component.rb +76 -0
  223. data/app/components/shadcn/popover_content_component.rb +25 -0
  224. data/app/components/shadcn/progress_component.rb +77 -0
  225. data/app/components/shadcn/radio_group_component.rb +129 -0
  226. data/app/components/shadcn/radio_group_item_component.rb +109 -0
  227. data/app/components/shadcn/resizable_handle_component.rb +98 -0
  228. data/app/components/shadcn/resizable_panel_component.rb +56 -0
  229. data/app/components/shadcn/resizable_panel_group_component.rb +94 -0
  230. data/app/components/shadcn/scroll_area_component.rb +110 -0
  231. data/app/components/shadcn/select_component.rb +151 -0
  232. data/app/components/shadcn/select_group_component.rb +32 -0
  233. data/app/components/shadcn/select_item_component.rb +59 -0
  234. data/app/components/shadcn/select_separator_component.rb +12 -0
  235. data/app/components/shadcn/separator_component.rb +54 -0
  236. data/app/components/shadcn/sheet_component.rb +82 -0
  237. data/app/components/shadcn/sheet_content_component.rb +95 -0
  238. data/app/components/shadcn/sheet_description_component.rb +12 -0
  239. data/app/components/shadcn/sheet_footer_component.rb +12 -0
  240. data/app/components/shadcn/sheet_header_component.rb +19 -0
  241. data/app/components/shadcn/sheet_title_component.rb +12 -0
  242. data/app/components/shadcn/sidebar_component.rb +180 -0
  243. data/app/components/shadcn/sidebar_content_component.rb +32 -0
  244. data/app/components/shadcn/sidebar_footer_component.rb +24 -0
  245. data/app/components/shadcn/sidebar_group_action_component.rb +26 -0
  246. data/app/components/shadcn/sidebar_group_component.rb +38 -0
  247. data/app/components/shadcn/sidebar_group_content_component.rb +32 -0
  248. data/app/components/shadcn/sidebar_group_label_component.rb +25 -0
  249. data/app/components/shadcn/sidebar_header_component.rb +24 -0
  250. data/app/components/shadcn/sidebar_inset_component.rb +25 -0
  251. data/app/components/shadcn/sidebar_menu_action_component.rb +37 -0
  252. data/app/components/shadcn/sidebar_menu_badge_component.rb +25 -0
  253. data/app/components/shadcn/sidebar_menu_button_component.rb +52 -0
  254. data/app/components/shadcn/sidebar_menu_component.rb +32 -0
  255. data/app/components/shadcn/sidebar_menu_item_component.rb +41 -0
  256. data/app/components/shadcn/sidebar_menu_skeleton_component.rb +46 -0
  257. data/app/components/shadcn/sidebar_menu_sub_button_component.rb +43 -0
  258. data/app/components/shadcn/sidebar_menu_sub_component.rb +33 -0
  259. data/app/components/shadcn/sidebar_menu_sub_item_component.rb +30 -0
  260. data/app/components/shadcn/sidebar_provider_component.rb +57 -0
  261. data/app/components/shadcn/sidebar_rail_component.rb +30 -0
  262. data/app/components/shadcn/sidebar_separator_component.rb +24 -0
  263. data/app/components/shadcn/sidebar_trigger_component.rb +51 -0
  264. data/app/components/shadcn/skeleton_component.rb +29 -0
  265. data/app/components/shadcn/slider_component.rb +76 -0
  266. data/app/components/shadcn/spinner_component.rb +67 -0
  267. data/app/components/shadcn/switch_component.rb +147 -0
  268. data/app/components/shadcn/table_body_component.rb +16 -0
  269. data/app/components/shadcn/table_caption_component.rb +12 -0
  270. data/app/components/shadcn/table_cell_component.rb +12 -0
  271. data/app/components/shadcn/table_component.rb +57 -0
  272. data/app/components/shadcn/table_footer_component.rb +16 -0
  273. data/app/components/shadcn/table_head_component.rb +12 -0
  274. data/app/components/shadcn/table_header_component.rb +16 -0
  275. data/app/components/shadcn/table_row_component.rb +40 -0
  276. data/app/components/shadcn/tabs_component.rb +78 -0
  277. data/app/components/shadcn/tabs_content_component.rb +32 -0
  278. data/app/components/shadcn/tabs_list_component.rb +30 -0
  279. data/app/components/shadcn/tabs_trigger_component.rb +37 -0
  280. data/app/components/shadcn/textarea_component.rb +84 -0
  281. data/app/components/shadcn/toast_action_component.rb +18 -0
  282. data/app/components/shadcn/toast_component.rb +114 -0
  283. data/app/components/shadcn/toast_description_component.rb +12 -0
  284. data/app/components/shadcn/toast_title_component.rb +12 -0
  285. data/app/components/shadcn/toast_viewport_component.rb +12 -0
  286. data/app/components/shadcn/toggle_component.rb +77 -0
  287. data/app/components/shadcn/toggle_group_component.rb +96 -0
  288. data/app/components/shadcn/toggle_group_item_component.rb +62 -0
  289. data/app/components/shadcn/tooltip_component.rb +89 -0
  290. data/app/components/shadcn/typography_component.rb +112 -0
  291. data/babel.config.cjs +5 -0
  292. data/bin/console +11 -0
  293. data/bin/setup +8 -0
  294. data/config/importmap.rb +5 -0
  295. data/fly.toml +26 -0
  296. data/jest.config.js +19 -0
  297. data/jest.setup.js +8 -0
  298. data/lib/generators/shadcn/component/component_generator.rb +188 -0
  299. data/lib/generators/shadcn/install/install_generator.rb +140 -0
  300. data/lib/generators/shadcn/install/templates/initializer.rb.tt +35 -0
  301. data/lib/generators/shadcn/install/templates/shadcn.yml.tt +35 -0
  302. data/lib/generators/shadcn/theme/theme_generator.rb +128 -0
  303. data/lib/shadcn/rails/class_merger.rb +228 -0
  304. data/lib/shadcn/rails/configuration.rb +341 -0
  305. data/lib/shadcn/rails/engine.rb +59 -0
  306. data/lib/shadcn/rails/helpers/class_name_helper.rb +35 -0
  307. data/lib/shadcn/rails/helpers/component_helper.rb +60 -0
  308. data/lib/shadcn/rails/helpers/pagination_helper.rb +187 -0
  309. data/lib/shadcn/rails/version.rb +7 -0
  310. data/lib/shadcn/rails.rb +179 -0
  311. data/package-lock.json +7415 -0
  312. data/package.json +68 -0
  313. data/rollup.config.js +29 -0
  314. data/sig/shadcn/rails.rbs +6 -0
  315. metadata +526 -0
@@ -0,0 +1,904 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ import AccordionController from "../../app/assets/javascripts/shadcn/controllers/accordion_controller.js"
3
+ import { click, wait, nextFrame, keydown, waitForEvent } from '../helpers/stimulus-test-helper.js'
4
+
5
+ describe("AccordionController", () => {
6
+ let application
7
+ let element
8
+ let controller
9
+
10
+ const createAccordionHTML = (type = "single", collapsible = false, defaultValue = "") => {
11
+ const collapsibleAttr = collapsible ? `data-shadcn--accordion-collapsible-value="true"` : ''
12
+ const defaultAttr = defaultValue ? `data-shadcn--accordion-default-value="${defaultValue}"` : ''
13
+
14
+ return `
15
+ <div data-controller="shadcn--accordion"
16
+ data-shadcn--accordion-type-value="${type}"
17
+ ${collapsibleAttr}
18
+ ${defaultAttr}>
19
+ <div data-shadcn--accordion-target="item" data-value="item-1" data-state="closed">
20
+ <button data-shadcn--accordion-target="trigger"
21
+ data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
22
+ aria-expanded="false">
23
+ Trigger 1
24
+ </button>
25
+ <div data-shadcn--accordion-target="content" hidden>Content 1</div>
26
+ </div>
27
+ <div data-shadcn--accordion-target="item" data-value="item-2" data-state="closed">
28
+ <button data-shadcn--accordion-target="trigger"
29
+ data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
30
+ aria-expanded="false">
31
+ Trigger 2
32
+ </button>
33
+ <div data-shadcn--accordion-target="content" hidden>Content 2</div>
34
+ </div>
35
+ <div data-shadcn--accordion-target="item" data-value="item-3" data-state="closed">
36
+ <button data-shadcn--accordion-target="trigger"
37
+ data-action="click->shadcn--accordion#toggle keydown->shadcn--accordion#handleKeydown"
38
+ aria-expanded="false">
39
+ Trigger 3
40
+ </button>
41
+ <div data-shadcn--accordion-target="content" hidden>Content 3</div>
42
+ </div>
43
+ </div>
44
+ `
45
+ }
46
+
47
+ beforeEach(async () => {
48
+ application = Application.start()
49
+ application.register("shadcn--accordion", AccordionController)
50
+ document.body.innerHTML = createAccordionHTML()
51
+
52
+ await nextFrame()
53
+
54
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
55
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
56
+ })
57
+
58
+ afterEach(() => {
59
+ if (application) {
60
+ application.stop()
61
+ }
62
+ document.body.innerHTML = ""
63
+ })
64
+
65
+ describe("value initialization", () => {
66
+ test("initializes with default type value of 'single'", () => {
67
+ expect(controller.typeValue).toBe("single")
68
+ })
69
+
70
+ test("initializes with default collapsible value of false", () => {
71
+ expect(controller.collapsibleValue).toBe(false)
72
+ })
73
+
74
+ test("initializes with empty default value", () => {
75
+ expect(controller.defaultValue).toBe("")
76
+ })
77
+
78
+ test("accepts custom type value", async () => {
79
+ application.stop()
80
+ document.body.innerHTML = createAccordionHTML("multiple")
81
+
82
+ application = Application.start()
83
+ application.register("shadcn--accordion", AccordionController)
84
+ await nextFrame()
85
+
86
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
87
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
88
+
89
+ expect(controller.typeValue).toBe("multiple")
90
+ })
91
+
92
+ test("accepts collapsible value", async () => {
93
+ application.stop()
94
+ document.body.innerHTML = createAccordionHTML("single", true)
95
+
96
+ application = Application.start()
97
+ application.register("shadcn--accordion", AccordionController)
98
+ await nextFrame()
99
+
100
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
101
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
102
+
103
+ expect(controller.collapsibleValue).toBe(true)
104
+ })
105
+ })
106
+
107
+ describe("default values", () => {
108
+ test("expands single item on connect with default value", async () => {
109
+ application.stop()
110
+ document.body.innerHTML = createAccordionHTML("single", false, "item-2")
111
+
112
+ application = Application.start()
113
+ application.register("shadcn--accordion", AccordionController)
114
+ await nextFrame()
115
+
116
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
117
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
118
+
119
+ const item2 = element.querySelector('[data-value="item-2"]')
120
+ expect(item2.dataset.state).toBe("open")
121
+ })
122
+
123
+ test("expands multiple items on connect with comma-separated default values", async () => {
124
+ application.stop()
125
+ document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-3")
126
+
127
+ application = Application.start()
128
+ application.register("shadcn--accordion", AccordionController)
129
+ await nextFrame()
130
+
131
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
132
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
133
+
134
+ const item1 = element.querySelector('[data-value="item-1"]')
135
+ const item3 = element.querySelector('[data-value="item-3"]')
136
+
137
+ expect(item1.dataset.state).toBe("open")
138
+ expect(item3.dataset.state).toBe("open")
139
+ })
140
+
141
+ test("handles whitespace in comma-separated default values", async () => {
142
+ application.stop()
143
+ document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-2 , item-3")
144
+
145
+ application = Application.start()
146
+ application.register("shadcn--accordion", AccordionController)
147
+ await nextFrame()
148
+
149
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
150
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
151
+
152
+ const item1 = element.querySelector('[data-value="item-1"]')
153
+ const item2 = element.querySelector('[data-value="item-2"]')
154
+ const item3 = element.querySelector('[data-value="item-3"]')
155
+
156
+ expect(item1.dataset.state).toBe("open")
157
+ expect(item2.dataset.state).toBe("open")
158
+ expect(item3.dataset.state).toBe("open")
159
+ })
160
+
161
+ test("ignores invalid default values gracefully", async () => {
162
+ application.stop()
163
+ document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, invalid-item, item-3")
164
+
165
+ application = Application.start()
166
+ application.register("shadcn--accordion", AccordionController)
167
+ await nextFrame()
168
+
169
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
170
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
171
+
172
+ const item1 = element.querySelector('[data-value="item-1"]')
173
+ const item3 = element.querySelector('[data-value="item-3"]')
174
+
175
+ // Valid items should still be expanded
176
+ expect(item1.dataset.state).toBe("open")
177
+ expect(item3.dataset.state).toBe("open")
178
+ })
179
+ })
180
+
181
+ describe("single mode", () => {
182
+ test("expands an item when clicked", () => {
183
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
184
+ const item1 = element.querySelector('[data-value="item-1"]')
185
+
186
+ click(trigger1)
187
+
188
+ expect(item1.dataset.state).toBe("open")
189
+ })
190
+
191
+ test("collapses other items when expanding one", async () => {
192
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
193
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
194
+ const item1 = element.querySelector('[data-value="item-1"]')
195
+ const item2 = element.querySelector('[data-value="item-2"]')
196
+
197
+ // Expand first item
198
+ click(trigger1)
199
+ expect(item1.dataset.state).toBe("open")
200
+
201
+ // Expand second item - should collapse first
202
+ click(trigger2)
203
+ await nextFrame()
204
+
205
+ expect(item2.dataset.state).toBe("open")
206
+ expect(item1.dataset.state).toBe("closed")
207
+ })
208
+
209
+ test("does not collapse open item when collapsible is false", () => {
210
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
211
+ const item1 = element.querySelector('[data-value="item-1"]')
212
+
213
+ // Expand item
214
+ click(trigger1)
215
+ expect(item1.dataset.state).toBe("open")
216
+
217
+ // Try to collapse - should remain open
218
+ click(trigger1)
219
+ expect(item1.dataset.state).toBe("open")
220
+ })
221
+
222
+ test("collapses open item when collapsible is true", async () => {
223
+ application.stop()
224
+ document.body.innerHTML = createAccordionHTML("single", true)
225
+
226
+ application = Application.start()
227
+ application.register("shadcn--accordion", AccordionController)
228
+ await nextFrame()
229
+
230
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
231
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
232
+
233
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
234
+ const item1 = element.querySelector('[data-value="item-1"]')
235
+
236
+ // Expand item
237
+ click(trigger1)
238
+ expect(item1.dataset.state).toBe("open")
239
+
240
+ // Collapse item
241
+ click(trigger1)
242
+ await nextFrame()
243
+
244
+ expect(item1.dataset.state).toBe("closed")
245
+ })
246
+
247
+ test("updates aria-expanded on trigger when expanding", () => {
248
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
249
+
250
+ click(trigger1)
251
+
252
+ expect(trigger1.getAttribute("aria-expanded")).toBe("true")
253
+ })
254
+
255
+ test("updates aria-expanded on trigger when collapsing", async () => {
256
+ application.stop()
257
+ document.body.innerHTML = createAccordionHTML("single", true)
258
+
259
+ application = Application.start()
260
+ application.register("shadcn--accordion", AccordionController)
261
+ await nextFrame()
262
+
263
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
264
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
265
+
266
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
267
+
268
+ // Expand then collapse
269
+ click(trigger1)
270
+ click(trigger1)
271
+ await nextFrame()
272
+
273
+ expect(trigger1.getAttribute("aria-expanded")).toBe("false")
274
+ })
275
+
276
+ test("sets data-state on content when expanding", () => {
277
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
278
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
279
+
280
+ click(trigger1)
281
+
282
+ expect(content1.dataset.state).toBe("open")
283
+ })
284
+
285
+ test("removes hidden attribute from content when expanding", () => {
286
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
287
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
288
+
289
+ click(trigger1)
290
+
291
+ expect(content1.hidden).toBe(false)
292
+ })
293
+ })
294
+
295
+ describe("multiple mode", () => {
296
+ beforeEach(async () => {
297
+ application.stop()
298
+ document.body.innerHTML = createAccordionHTML("multiple")
299
+
300
+ application = Application.start()
301
+ application.register("shadcn--accordion", AccordionController)
302
+ await nextFrame()
303
+
304
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
305
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
306
+ })
307
+
308
+ test("can expand multiple items simultaneously", () => {
309
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
310
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
311
+ const item1 = element.querySelector('[data-value="item-1"]')
312
+ const item2 = element.querySelector('[data-value="item-2"]')
313
+
314
+ click(trigger1)
315
+ click(trigger2)
316
+
317
+ expect(item1.dataset.state).toBe("open")
318
+ expect(item2.dataset.state).toBe("open")
319
+ })
320
+
321
+ test("does not collapse other items when expanding", () => {
322
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
323
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
324
+ const trigger3 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[2]
325
+ const item1 = element.querySelector('[data-value="item-1"]')
326
+ const item2 = element.querySelector('[data-value="item-2"]')
327
+ const item3 = element.querySelector('[data-value="item-3"]')
328
+
329
+ click(trigger1)
330
+ click(trigger2)
331
+ click(trigger3)
332
+
333
+ expect(item1.dataset.state).toBe("open")
334
+ expect(item2.dataset.state).toBe("open")
335
+ expect(item3.dataset.state).toBe("open")
336
+ })
337
+
338
+ test("can collapse individual items", async () => {
339
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
340
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
341
+ const item1 = element.querySelector('[data-value="item-1"]')
342
+ const item2 = element.querySelector('[data-value="item-2"]')
343
+
344
+ // Expand both
345
+ click(trigger1)
346
+ click(trigger2)
347
+
348
+ // Collapse first
349
+ click(trigger1)
350
+ await nextFrame()
351
+
352
+ expect(item1.dataset.state).toBe("closed")
353
+ expect(item2.dataset.state).toBe("open")
354
+ })
355
+
356
+ test("can expand all items then collapse all", async () => {
357
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
358
+ const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
359
+
360
+ // Expand all
361
+ triggers.forEach(trigger => click(trigger))
362
+ items.forEach(item => expect(item.dataset.state).toBe("open"))
363
+
364
+ // Collapse all
365
+ triggers.forEach(trigger => click(trigger))
366
+ await nextFrame()
367
+
368
+ items.forEach(item => expect(item.dataset.state).toBe("closed"))
369
+ })
370
+ })
371
+
372
+ describe("type safety", () => {
373
+ test("findItemByValue returns correct item", () => {
374
+ const item2 = controller.findItemByValue("item-2")
375
+ expect(item2.dataset.value).toBe("item-2")
376
+ })
377
+
378
+ test("findItemByValue returns undefined for non-existent item", () => {
379
+ const item = controller.findItemByValue("non-existent")
380
+ expect(item).toBeUndefined()
381
+ })
382
+
383
+ test("handles missing item gracefully in expandItem", () => {
384
+ // Attempt to expand non-existent item should not throw
385
+ expect(() => {
386
+ const fakeItem = document.createElement('div')
387
+ controller.expandItem(fakeItem)
388
+ }).not.toThrow()
389
+ })
390
+
391
+ test("handles missing trigger gracefully", () => {
392
+ const item = document.createElement('div')
393
+ item.setAttribute('data-shadcn--accordion-target', 'item')
394
+
395
+ expect(() => {
396
+ controller.expandItem(item)
397
+ }).not.toThrow()
398
+ })
399
+
400
+ test("handles missing content gracefully", () => {
401
+ const item = document.createElement('div')
402
+ item.setAttribute('data-shadcn--accordion-target', 'item')
403
+
404
+ const trigger = document.createElement('button')
405
+ trigger.setAttribute('data-shadcn--accordion-target', 'trigger')
406
+ item.appendChild(trigger)
407
+
408
+ expect(() => {
409
+ controller.expandItem(item)
410
+ }).not.toThrow()
411
+ })
412
+ })
413
+
414
+ describe("keyboard navigation", () => {
415
+ beforeEach(() => {
416
+ // Focus first trigger
417
+ const firstTrigger = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
418
+ firstTrigger.focus()
419
+ })
420
+
421
+ test("ArrowDown moves focus to next trigger", () => {
422
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
423
+ triggers[0].focus()
424
+
425
+ keydown(triggers[0], 'ArrowDown')
426
+
427
+ expect(document.activeElement).toBe(triggers[1])
428
+ })
429
+
430
+ test("ArrowUp moves focus to previous trigger", () => {
431
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
432
+ triggers[1].focus()
433
+
434
+ keydown(triggers[1], 'ArrowUp')
435
+
436
+ expect(document.activeElement).toBe(triggers[0])
437
+ })
438
+
439
+ test("ArrowDown wraps to first trigger from last", () => {
440
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
441
+ triggers[2].focus()
442
+
443
+ keydown(triggers[2], 'ArrowDown')
444
+
445
+ expect(document.activeElement).toBe(triggers[0])
446
+ })
447
+
448
+ test("ArrowUp wraps to last trigger from first", () => {
449
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
450
+ triggers[0].focus()
451
+
452
+ keydown(triggers[0], 'ArrowUp')
453
+
454
+ expect(document.activeElement).toBe(triggers[2])
455
+ })
456
+
457
+ test("Home moves focus to first trigger", () => {
458
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
459
+ triggers[2].focus()
460
+
461
+ keydown(triggers[2], 'Home')
462
+
463
+ expect(document.activeElement).toBe(triggers[0])
464
+ })
465
+
466
+ test("End moves focus to last trigger", () => {
467
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
468
+ triggers[0].focus()
469
+
470
+ keydown(triggers[0], 'End')
471
+
472
+ expect(document.activeElement).toBe(triggers[2])
473
+ })
474
+
475
+ test("other keys do not move focus", () => {
476
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
477
+ triggers[0].focus()
478
+
479
+ keydown(triggers[0], 'Tab')
480
+ expect(document.activeElement).toBe(triggers[0])
481
+
482
+ keydown(triggers[0], 'Enter')
483
+ expect(document.activeElement).toBe(triggers[0])
484
+
485
+ keydown(triggers[0], 'Escape')
486
+ expect(document.activeElement).toBe(triggers[0])
487
+ })
488
+
489
+ test("ArrowDown prevents default behavior", () => {
490
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
491
+ triggers[0].focus()
492
+
493
+ let defaultPrevented = false
494
+ const event = new KeyboardEvent('keydown', {
495
+ key: 'ArrowDown',
496
+ bubbles: true,
497
+ cancelable: true
498
+ })
499
+
500
+ // Override preventDefault to track if it was called
501
+ event.preventDefault = () => { defaultPrevented = true }
502
+ triggers[0].dispatchEvent(event)
503
+
504
+ expect(defaultPrevented).toBe(true)
505
+ })
506
+
507
+ test("ArrowUp prevents default behavior", () => {
508
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
509
+ triggers[0].focus()
510
+
511
+ let defaultPrevented = false
512
+ const event = new KeyboardEvent('keydown', {
513
+ key: 'ArrowUp',
514
+ bubbles: true,
515
+ cancelable: true
516
+ })
517
+
518
+ // Override preventDefault to track if it was called
519
+ event.preventDefault = () => { defaultPrevented = true }
520
+ triggers[0].dispatchEvent(event)
521
+
522
+ expect(defaultPrevented).toBe(true)
523
+ })
524
+
525
+ test("Home prevents default behavior", () => {
526
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
527
+ triggers[0].focus()
528
+
529
+ let defaultPrevented = false
530
+ const event = new KeyboardEvent('keydown', {
531
+ key: 'Home',
532
+ bubbles: true,
533
+ cancelable: true
534
+ })
535
+
536
+ // Override preventDefault to track if it was called
537
+ event.preventDefault = () => { defaultPrevented = true }
538
+ triggers[0].dispatchEvent(event)
539
+
540
+ expect(defaultPrevented).toBe(true)
541
+ })
542
+
543
+ test("End prevents default behavior", () => {
544
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
545
+ triggers[0].focus()
546
+
547
+ let defaultPrevented = false
548
+ const event = new KeyboardEvent('keydown', {
549
+ key: 'End',
550
+ bubbles: true,
551
+ cancelable: true
552
+ })
553
+
554
+ // Override preventDefault to track if it was called
555
+ event.preventDefault = () => { defaultPrevented = true }
556
+ triggers[0].dispatchEvent(event)
557
+
558
+ expect(defaultPrevented).toBe(true)
559
+ })
560
+
561
+ test("does nothing when no trigger is focused", () => {
562
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
563
+
564
+ // Create a separate element to focus that's not a trigger
565
+ const outsideElement = document.createElement('button')
566
+ document.body.appendChild(outsideElement)
567
+ outsideElement.focus()
568
+
569
+ const initialActiveElement = document.activeElement
570
+
571
+ // Dispatch keydown on a trigger but it's not focused
572
+ keydown(triggers[0], 'ArrowDown')
573
+
574
+ // Active element should remain the outside element
575
+ expect(document.activeElement).toBe(initialActiveElement)
576
+ expect(document.activeElement).not.toBe(triggers[1])
577
+
578
+ // Cleanup
579
+ document.body.removeChild(outsideElement)
580
+ })
581
+ })
582
+
583
+ describe("event dispatch", () => {
584
+ test("dispatches expand event when item is expanded", async () => {
585
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
586
+
587
+ const eventPromise = waitForEvent(element, 'shadcn--accordion:expand')
588
+
589
+ click(trigger1)
590
+
591
+ const event = await eventPromise
592
+ expect(event.detail.value).toBe("item-1")
593
+ })
594
+
595
+ test("dispatches collapse event when item is collapsed", async () => {
596
+ application.stop()
597
+ document.body.innerHTML = createAccordionHTML("single", true)
598
+
599
+ application = Application.start()
600
+ application.register("shadcn--accordion", AccordionController)
601
+ await nextFrame()
602
+
603
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
604
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
605
+
606
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
607
+
608
+ // Expand first
609
+ click(trigger1)
610
+
611
+ const eventPromise = waitForEvent(element, 'shadcn--accordion:collapse')
612
+
613
+ // Then collapse
614
+ click(trigger1)
615
+
616
+ const event = await eventPromise
617
+ expect(event.detail.value).toBe("item-1")
618
+ })
619
+
620
+ test("dispatches collapse event when another item is expanded in single mode", async () => {
621
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
622
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
623
+
624
+ // Expand first item
625
+ click(trigger1)
626
+
627
+ const eventPromise = waitForEvent(element, 'shadcn--accordion:collapse')
628
+
629
+ // Expand second item (should collapse first)
630
+ click(trigger2)
631
+
632
+ const event = await eventPromise
633
+ expect(event.detail.value).toBe("item-1")
634
+ })
635
+
636
+ test("dispatches events with correct value detail", async () => {
637
+ const trigger3 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[2]
638
+
639
+ const eventPromise = waitForEvent(element, 'shadcn--accordion:expand')
640
+
641
+ click(trigger3)
642
+
643
+ const event = await eventPromise
644
+ expect(event.detail.value).toBe("item-3")
645
+ })
646
+
647
+ test("expand event is dispatched before collapse event in single mode", async () => {
648
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
649
+ const trigger2 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[1]
650
+
651
+ const events = []
652
+
653
+ element.addEventListener('shadcn--accordion:expand', (e) => {
654
+ events.push({ type: 'expand', value: e.detail.value })
655
+ })
656
+
657
+ element.addEventListener('shadcn--accordion:collapse', (e) => {
658
+ events.push({ type: 'collapse', value: e.detail.value })
659
+ })
660
+
661
+ // Expand first item
662
+ click(trigger1)
663
+ await nextFrame()
664
+
665
+ // Expand second item
666
+ click(trigger2)
667
+ await nextFrame()
668
+
669
+ // Based on the implementation, collapse happens first, then expand
670
+ // This is because collapseItem is called before expandItem in toggle()
671
+ expect(events).toEqual([
672
+ { type: 'expand', value: 'item-1' },
673
+ { type: 'collapse', value: 'item-1' },
674
+ { type: 'expand', value: 'item-2' }
675
+ ])
676
+ })
677
+ })
678
+
679
+ describe("animation", () => {
680
+ test("sets height on content during expand animation", () => {
681
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
682
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
683
+
684
+ click(trigger1)
685
+
686
+ // After requestAnimationFrame, height should be set
687
+ expect(content1.style.height).toBeTruthy()
688
+ })
689
+
690
+ test("sets height to 0px initially during collapse", async () => {
691
+ application.stop()
692
+ document.body.innerHTML = createAccordionHTML("single", true)
693
+
694
+ application = Application.start()
695
+ application.register("shadcn--accordion", AccordionController)
696
+ await nextFrame()
697
+
698
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
699
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
700
+
701
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
702
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
703
+
704
+ // Expand first
705
+ click(trigger1)
706
+ await nextFrame()
707
+
708
+ // Collapse
709
+ click(trigger1)
710
+ await nextFrame()
711
+
712
+ expect(content1.style.height).toBe('0px')
713
+ })
714
+
715
+ test("sets hidden attribute after collapse animation", async () => {
716
+ application.stop()
717
+ document.body.innerHTML = createAccordionHTML("single", true)
718
+
719
+ application = Application.start()
720
+ application.register("shadcn--accordion", AccordionController)
721
+ await nextFrame()
722
+
723
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
724
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
725
+
726
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
727
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
728
+
729
+ // Expand first
730
+ click(trigger1)
731
+ await nextFrame()
732
+
733
+ // Collapse
734
+ click(trigger1)
735
+
736
+ // Wait for animation (200ms)
737
+ await wait(250)
738
+
739
+ expect(content1.hidden).toBe(true)
740
+ })
741
+
742
+ test("removes fixed height after expand animation", async () => {
743
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
744
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
745
+
746
+ click(trigger1)
747
+
748
+ // Wait for animation to complete (200ms)
749
+ await wait(250)
750
+
751
+ expect(content1.style.height).toBe('')
752
+ })
753
+ })
754
+
755
+ describe("data-state attributes", () => {
756
+ test("item has data-state='closed' initially", () => {
757
+ const item1 = element.querySelector('[data-value="item-1"]')
758
+ expect(item1.dataset.state).toBe("closed")
759
+ })
760
+
761
+ test("item has data-state='open' when expanded", () => {
762
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
763
+ const item1 = element.querySelector('[data-value="item-1"]')
764
+
765
+ click(trigger1)
766
+
767
+ expect(item1.dataset.state).toBe("open")
768
+ })
769
+
770
+ test("trigger has data-state='open' when item is expanded", () => {
771
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
772
+
773
+ click(trigger1)
774
+
775
+ expect(trigger1.dataset.state).toBe("open")
776
+ })
777
+
778
+ test("content has data-state='open' when item is expanded", () => {
779
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
780
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
781
+
782
+ click(trigger1)
783
+
784
+ expect(content1.dataset.state).toBe("open")
785
+ })
786
+
787
+ test("all elements have data-state='closed' when collapsed in collapsible mode", async () => {
788
+ application.stop()
789
+ document.body.innerHTML = createAccordionHTML("single", true)
790
+
791
+ application = Application.start()
792
+ application.register("shadcn--accordion", AccordionController)
793
+ await nextFrame()
794
+
795
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
796
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
797
+
798
+ const trigger1 = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')[0]
799
+ const item1 = element.querySelector('[data-value="item-1"]')
800
+ const content1 = element.querySelectorAll('[data-shadcn--accordion-target="content"]')[0]
801
+
802
+ // Expand then collapse
803
+ click(trigger1)
804
+ click(trigger1)
805
+ await nextFrame()
806
+
807
+ expect(item1.dataset.state).toBe("closed")
808
+ expect(trigger1.dataset.state).toBe("closed")
809
+ expect(content1.dataset.state).toBe("closed")
810
+ })
811
+ })
812
+
813
+ describe("integration scenarios", () => {
814
+ test("can toggle between items in single mode", async () => {
815
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
816
+ const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
817
+
818
+ // Expand item 1
819
+ click(triggers[0])
820
+ expect(items[0].dataset.state).toBe("open")
821
+ expect(items[1].dataset.state).toBe("closed")
822
+ expect(items[2].dataset.state).toBe("closed")
823
+
824
+ // Expand item 2
825
+ click(triggers[1])
826
+ await nextFrame()
827
+
828
+ expect(items[0].dataset.state).toBe("closed")
829
+ expect(items[1].dataset.state).toBe("open")
830
+ expect(items[2].dataset.state).toBe("closed")
831
+
832
+ // Expand item 3
833
+ click(triggers[2])
834
+ await nextFrame()
835
+
836
+ expect(items[0].dataset.state).toBe("closed")
837
+ expect(items[1].dataset.state).toBe("closed")
838
+ expect(items[2].dataset.state).toBe("open")
839
+ })
840
+
841
+ test("can expand and collapse all items in multiple mode", async () => {
842
+ application.stop()
843
+ document.body.innerHTML = createAccordionHTML("multiple")
844
+
845
+ application = Application.start()
846
+ application.register("shadcn--accordion", AccordionController)
847
+ await nextFrame()
848
+
849
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
850
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
851
+
852
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
853
+ const items = element.querySelectorAll('[data-shadcn--accordion-target="item"]')
854
+
855
+ // Expand all
856
+ triggers.forEach(trigger => click(trigger))
857
+ items.forEach(item => expect(item.dataset.state).toBe("open"))
858
+
859
+ // Collapse all
860
+ triggers.forEach(trigger => click(trigger))
861
+ await nextFrame()
862
+
863
+ items.forEach(item => expect(item.dataset.state).toBe("closed"))
864
+ })
865
+
866
+ test("keyboard navigation works with expanded items", () => {
867
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
868
+
869
+ // Expand first item
870
+ click(triggers[0])
871
+
872
+ // Focus first trigger and navigate
873
+ triggers[0].focus()
874
+ keydown(triggers[0], 'ArrowDown')
875
+
876
+ expect(document.activeElement).toBe(triggers[1])
877
+ })
878
+
879
+ test("can expand default items and navigate with keyboard", async () => {
880
+ application.stop()
881
+ document.body.innerHTML = createAccordionHTML("multiple", false, "item-1, item-3")
882
+
883
+ application = Application.start()
884
+ application.register("shadcn--accordion", AccordionController)
885
+ await nextFrame()
886
+
887
+ element = document.querySelector('[data-controller="shadcn--accordion"]')
888
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--accordion")
889
+
890
+ const triggers = element.querySelectorAll('[data-shadcn--accordion-target="trigger"]')
891
+ const item1 = element.querySelector('[data-value="item-1"]')
892
+ const item3 = element.querySelector('[data-value="item-3"]')
893
+
894
+ // Verify defaults are expanded
895
+ expect(item1.dataset.state).toBe("open")
896
+ expect(item3.dataset.state).toBe("open")
897
+
898
+ // Navigate with keyboard
899
+ triggers[0].focus()
900
+ keydown(triggers[0], 'End')
901
+ expect(document.activeElement).toBe(triggers[2])
902
+ })
903
+ })
904
+ })