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,808 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ import TooltipController from "../../app/assets/javascripts/shadcn/controllers/tooltip_controller.js"
3
+ import { wait, nextFrame, keydown } from '../helpers/stimulus-test-helper.js'
4
+
5
+ describe("TooltipController", () => {
6
+ let application
7
+ let element
8
+ let controller
9
+
10
+ const createTooltipHTML = (side = "top", align = "center", delay = 200, skipDelay = 300) => {
11
+ return `
12
+ <div data-controller="shadcn--tooltip"
13
+ data-shadcn--tooltip-side-value="${side}"
14
+ data-shadcn--tooltip-align-value="${align}"
15
+ data-shadcn--tooltip-delay-value="${delay}"
16
+ data-shadcn--tooltip-skip-delay-value="${skipDelay}">
17
+ <button data-shadcn--tooltip-target="trigger"
18
+ data-action="mouseenter->shadcn--tooltip#show mouseleave->shadcn--tooltip#hide focus->shadcn--tooltip#show blur->shadcn--tooltip#hide">
19
+ Hover me
20
+ </button>
21
+ <div data-shadcn--tooltip-target="content"
22
+ hidden
23
+ role="tooltip"
24
+ style="position: relative;">
25
+ Tooltip content
26
+ </div>
27
+ </div>
28
+ `
29
+ }
30
+
31
+ beforeEach(async () => {
32
+ application = Application.start()
33
+ application.register("shadcn--tooltip", TooltipController)
34
+ document.body.innerHTML = createTooltipHTML()
35
+
36
+ await nextFrame()
37
+
38
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
39
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
40
+ })
41
+
42
+ afterEach(() => {
43
+ if (application) {
44
+ application.stop()
45
+ }
46
+ document.body.innerHTML = ""
47
+ })
48
+
49
+ describe("value initialization", () => {
50
+ test("initializes with default side value of 'top'", () => {
51
+ expect(controller.sideValue).toBe("top")
52
+ })
53
+
54
+ test("initializes with default align value of 'center'", () => {
55
+ expect(controller.alignValue).toBe("center")
56
+ })
57
+
58
+ test("initializes with default delay value of 200", () => {
59
+ expect(controller.delayValue).toBe(200)
60
+ })
61
+
62
+ test("initializes with default skipDelay value of 300", () => {
63
+ expect(controller.skipDelayValue).toBe(300)
64
+ })
65
+
66
+ test("accepts custom side value", async () => {
67
+ application.stop()
68
+ document.body.innerHTML = createTooltipHTML("bottom")
69
+
70
+ application = Application.start()
71
+ application.register("shadcn--tooltip", TooltipController)
72
+ await nextFrame()
73
+
74
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
75
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
76
+
77
+ expect(controller.sideValue).toBe("bottom")
78
+ })
79
+
80
+ test("accepts custom align value", async () => {
81
+ application.stop()
82
+ document.body.innerHTML = createTooltipHTML("top", "start")
83
+
84
+ application = Application.start()
85
+ application.register("shadcn--tooltip", TooltipController)
86
+ await nextFrame()
87
+
88
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
89
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
90
+
91
+ expect(controller.alignValue).toBe("start")
92
+ })
93
+
94
+ test("accepts custom delay value", async () => {
95
+ application.stop()
96
+ document.body.innerHTML = createTooltipHTML("top", "center", 500)
97
+
98
+ application = Application.start()
99
+ application.register("shadcn--tooltip", TooltipController)
100
+ await nextFrame()
101
+
102
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
103
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
104
+
105
+ expect(controller.delayValue).toBe(500)
106
+ })
107
+
108
+ test("accepts custom skipDelay value", async () => {
109
+ application.stop()
110
+ document.body.innerHTML = createTooltipHTML("top", "center", 200, 1000)
111
+
112
+ application = Application.start()
113
+ application.register("shadcn--tooltip", TooltipController)
114
+ await nextFrame()
115
+
116
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
117
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
118
+
119
+ expect(controller.skipDelayValue).toBe(1000)
120
+ })
121
+ })
122
+
123
+ describe("connect and disconnect", () => {
124
+ test("initializes timeouts to null on connect", () => {
125
+ expect(controller.showTimeout).toBeNull()
126
+ expect(controller.hideTimeout).toBeNull()
127
+ })
128
+
129
+ test("clears timeouts on disconnect", async () => {
130
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
131
+
132
+ // Start showing tooltip
133
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
134
+
135
+ expect(controller.showTimeout).not.toBeNull()
136
+
137
+ // Disconnect controller
138
+ controller.disconnect()
139
+
140
+ expect(controller.showTimeout).toBeNull()
141
+ expect(controller.hideTimeout).toBeNull()
142
+ })
143
+ })
144
+
145
+ describe("show and hide on hover", () => {
146
+ test("shows tooltip on mouseenter", async () => {
147
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
148
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
149
+
150
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
151
+
152
+ // Wait for delay
153
+ await wait(250)
154
+
155
+ expect(content.hidden).toBe(false)
156
+ expect(content.dataset.state).toBe("open")
157
+ })
158
+
159
+ test("hides tooltip on mouseleave", async () => {
160
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
161
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
162
+
163
+ // Show tooltip
164
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
165
+ await wait(250)
166
+
167
+ expect(content.hidden).toBe(false)
168
+
169
+ // Hide tooltip
170
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
171
+ await wait(150)
172
+
173
+ expect(content.dataset.state).toBe("closed")
174
+ expect(content.hidden).toBe(true)
175
+ })
176
+
177
+ test("does not show tooltip if mouseleave before delay", async () => {
178
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
179
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
180
+
181
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
182
+
183
+ // Leave before delay completes
184
+ await wait(100)
185
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
186
+
187
+ // Wait past original delay
188
+ await wait(200)
189
+
190
+ expect(content.hidden).toBe(true)
191
+ })
192
+ })
193
+
194
+ describe("delay behavior", () => {
195
+ test("waits for delay before showing tooltip", async () => {
196
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
197
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
198
+
199
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
200
+
201
+ // Tooltip should not be visible before delay
202
+ await wait(100)
203
+ expect(content.hidden).toBe(true)
204
+
205
+ // Should be visible after delay
206
+ await wait(150)
207
+ expect(content.hidden).toBe(false)
208
+ })
209
+
210
+ test("respects custom delay value", async () => {
211
+ application.stop()
212
+ document.body.innerHTML = createTooltipHTML("top", "center", 500)
213
+
214
+ application = Application.start()
215
+ application.register("shadcn--tooltip", TooltipController)
216
+ await nextFrame()
217
+
218
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
219
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
220
+
221
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
222
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
223
+
224
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
225
+
226
+ // Should not show before custom delay
227
+ await wait(400)
228
+ expect(content.hidden).toBe(true)
229
+
230
+ // Should show after custom delay
231
+ await wait(150)
232
+ expect(content.hidden).toBe(false)
233
+ })
234
+ })
235
+
236
+ describe("positioning - side", () => {
237
+ test("positions tooltip on top by default", async () => {
238
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
239
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
240
+
241
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
242
+ await wait(250)
243
+
244
+ expect(content.style.bottom).toBe("100%")
245
+ expect(content.style.marginBottom).toBe("8px")
246
+ expect(content.dataset.side).toBe("top")
247
+ })
248
+
249
+ test("positions tooltip on bottom", async () => {
250
+ application.stop()
251
+ document.body.innerHTML = createTooltipHTML("bottom")
252
+
253
+ application = Application.start()
254
+ application.register("shadcn--tooltip", TooltipController)
255
+ await nextFrame()
256
+
257
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
258
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
259
+
260
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
261
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
262
+
263
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
264
+ await wait(250)
265
+
266
+ expect(content.style.top).toBe("100%")
267
+ expect(content.style.marginTop).toBe("8px")
268
+ expect(content.dataset.side).toBe("bottom")
269
+ })
270
+
271
+ test("positions tooltip on left", async () => {
272
+ application.stop()
273
+ document.body.innerHTML = createTooltipHTML("left")
274
+
275
+ application = Application.start()
276
+ application.register("shadcn--tooltip", TooltipController)
277
+ await nextFrame()
278
+
279
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
280
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
281
+
282
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
283
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
284
+
285
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
286
+ await wait(250)
287
+
288
+ expect(content.style.right).toBe("100%")
289
+ expect(content.style.marginRight).toBe("8px")
290
+ expect(content.style.top).toBe("50%")
291
+ expect(content.style.transform).toBe("translateY(-50%)")
292
+ expect(content.dataset.side).toBe("left")
293
+ })
294
+
295
+ test("positions tooltip on right", async () => {
296
+ application.stop()
297
+ document.body.innerHTML = createTooltipHTML("right")
298
+
299
+ application = Application.start()
300
+ application.register("shadcn--tooltip", TooltipController)
301
+ await nextFrame()
302
+
303
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
304
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
305
+
306
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
307
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
308
+
309
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
310
+ await wait(250)
311
+
312
+ expect(content.style.left).toBe("100%")
313
+ expect(content.style.marginLeft).toBe("8px")
314
+ expect(content.style.top).toBe("50%")
315
+ expect(content.style.transform).toBe("translateY(-50%)")
316
+ expect(content.dataset.side).toBe("right")
317
+ })
318
+ })
319
+
320
+ describe("positioning - align", () => {
321
+ test("centers tooltip by default on top/bottom", async () => {
322
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
323
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
324
+
325
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
326
+ await wait(250)
327
+
328
+ expect(content.style.left).toBe("50%")
329
+ expect(content.style.transform).toBe("translateX(-50%)")
330
+ })
331
+
332
+ test("aligns tooltip to start on top", async () => {
333
+ application.stop()
334
+ document.body.innerHTML = createTooltipHTML("top", "start")
335
+
336
+ application = Application.start()
337
+ application.register("shadcn--tooltip", TooltipController)
338
+ await nextFrame()
339
+
340
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
341
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
342
+
343
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
344
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
345
+
346
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
347
+ await wait(250)
348
+
349
+ expect(content.style.left).toBe("0px")
350
+ })
351
+
352
+ test("aligns tooltip to end on top", async () => {
353
+ application.stop()
354
+ document.body.innerHTML = createTooltipHTML("top", "end")
355
+
356
+ application = Application.start()
357
+ application.register("shadcn--tooltip", TooltipController)
358
+ await nextFrame()
359
+
360
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
361
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
362
+
363
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
364
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
365
+
366
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
367
+ await wait(250)
368
+
369
+ expect(content.style.right).toBe("0px")
370
+ })
371
+
372
+ test("centers tooltip on bottom", async () => {
373
+ application.stop()
374
+ document.body.innerHTML = createTooltipHTML("bottom", "center")
375
+
376
+ application = Application.start()
377
+ application.register("shadcn--tooltip", TooltipController)
378
+ await nextFrame()
379
+
380
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
381
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
382
+
383
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
384
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
385
+
386
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
387
+ await wait(250)
388
+
389
+ expect(content.style.left).toBe("50%")
390
+ expect(content.style.transform).toBe("translateX(-50%)")
391
+ })
392
+
393
+ test("does not apply horizontal alignment on left/right sides", async () => {
394
+ application.stop()
395
+ document.body.innerHTML = createTooltipHTML("left", "start")
396
+
397
+ application = Application.start()
398
+ application.register("shadcn--tooltip", TooltipController)
399
+ await nextFrame()
400
+
401
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
402
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
403
+
404
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
405
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
406
+
407
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
408
+ await wait(250)
409
+
410
+ // Left/right sides only set vertical positioning
411
+ expect(content.style.top).toBe("50%")
412
+ expect(content.style.transform).toBe("translateY(-50%)")
413
+ // Should not have left/right alignment
414
+ expect(content.style.left).toBe("")
415
+ })
416
+ })
417
+
418
+ describe("timeout cleanup", () => {
419
+ test("clears show timeout when hide is called", async () => {
420
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
421
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
422
+
423
+ // Start showing
424
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
425
+ expect(controller.showTimeout).not.toBeNull()
426
+
427
+ // Hide before show completes
428
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
429
+ expect(controller.showTimeout).toBeNull()
430
+
431
+ // Advance past original show delay
432
+ await wait(300)
433
+
434
+ // Tooltip should not be shown
435
+ expect(content.hidden).toBe(true)
436
+ })
437
+
438
+ test("clears hide timeout when show is called", async () => {
439
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
440
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
441
+
442
+ // Show tooltip
443
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
444
+ await wait(250)
445
+
446
+ // Start hiding
447
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
448
+ expect(controller.hideTimeout).not.toBeNull()
449
+
450
+ // Show again before hide completes
451
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
452
+ expect(controller.hideTimeout).toBeNull()
453
+ })
454
+
455
+ test("no memory leaks when quickly hovering in and out", async () => {
456
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
457
+
458
+ // Rapidly hover in/out
459
+ for (let i = 0; i < 10; i++) {
460
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
461
+ await wait(50)
462
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
463
+ await wait(50)
464
+ }
465
+
466
+ // Only one timeout should be active at most
467
+ const hasTimeout = controller.showTimeout !== null || controller.hideTimeout !== null
468
+ expect(hasTimeout).toBe(true)
469
+
470
+ // Clean up
471
+ await wait(300)
472
+ })
473
+
474
+ test("clears all timeouts in clearTimeouts method", () => {
475
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
476
+
477
+ // Create a show timeout
478
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
479
+ expect(controller.showTimeout).not.toBeNull()
480
+
481
+ // Call clearTimeouts
482
+ controller.clearTimeouts()
483
+
484
+ expect(controller.showTimeout).toBeNull()
485
+ expect(controller.hideTimeout).toBeNull()
486
+ })
487
+ })
488
+
489
+ describe("keyboard accessibility", () => {
490
+ test("shows tooltip on focus", async () => {
491
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
492
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
493
+
494
+ trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
495
+
496
+ await wait(250)
497
+
498
+ expect(content.hidden).toBe(false)
499
+ expect(content.dataset.state).toBe("open")
500
+ })
501
+
502
+ test("hides tooltip on blur", async () => {
503
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
504
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
505
+
506
+ // Show tooltip
507
+ trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
508
+ await wait(250)
509
+
510
+ expect(content.hidden).toBe(false)
511
+
512
+ // Hide tooltip
513
+ trigger.dispatchEvent(new FocusEvent('blur', { bubbles: true }))
514
+ await wait(150)
515
+
516
+ expect(content.dataset.state).toBe("closed")
517
+ expect(content.hidden).toBe(true)
518
+ })
519
+
520
+ test("shows tooltip with keyboard navigation", async () => {
521
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
522
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
523
+
524
+ // Tab to focus the button
525
+ trigger.focus()
526
+ trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
527
+
528
+ await wait(250)
529
+
530
+ expect(content.hidden).toBe(false)
531
+ })
532
+ })
533
+
534
+ describe("ARIA attributes", () => {
535
+ test("tooltip has role='tooltip'", () => {
536
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
537
+ expect(content.getAttribute('role')).toBe('tooltip')
538
+ })
539
+
540
+ test("content is hidden initially", () => {
541
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
542
+ expect(content.hidden).toBe(true)
543
+ })
544
+
545
+ test("content data-state changes to open when shown", async () => {
546
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
547
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
548
+
549
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
550
+ await wait(250)
551
+
552
+ expect(content.dataset.state).toBe("open")
553
+ })
554
+
555
+ test("content data-state changes to closed when hidden", async () => {
556
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
557
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
558
+
559
+ // Show
560
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
561
+ await wait(250)
562
+
563
+ // Hide
564
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
565
+ await wait(50)
566
+
567
+ expect(content.dataset.state).toBe("closed")
568
+ })
569
+ })
570
+
571
+ describe("positioning edge cases", () => {
572
+ test("handles missing trigger target gracefully", async () => {
573
+ // Remove trigger
574
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
575
+ trigger.remove()
576
+
577
+ expect(() => {
578
+ controller.show()
579
+ }).not.toThrow()
580
+
581
+ await wait(250)
582
+ })
583
+
584
+ test("handles missing content target gracefully", async () => {
585
+ // Remove content
586
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
587
+ content.remove()
588
+
589
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
590
+
591
+ expect(() => {
592
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
593
+ }).not.toThrow()
594
+
595
+ await wait(250)
596
+ })
597
+
598
+ test("resets positioning styles before positioning", async () => {
599
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
600
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
601
+
602
+ // Show tooltip
603
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
604
+ await wait(250)
605
+
606
+ // Verify position is absolute
607
+ expect(content.style.position).toBe("absolute")
608
+ })
609
+
610
+ test("sets data-side attribute on content", async () => {
611
+ application.stop()
612
+ document.body.innerHTML = createTooltipHTML("bottom")
613
+
614
+ application = Application.start()
615
+ application.register("shadcn--tooltip", TooltipController)
616
+ await nextFrame()
617
+
618
+ element = document.querySelector('[data-controller="shadcn--tooltip"]')
619
+ controller = application.getControllerForElementAndIdentifier(element, "shadcn--tooltip")
620
+
621
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
622
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
623
+
624
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
625
+ await wait(250)
626
+
627
+ expect(content.dataset.side).toBe("bottom")
628
+ })
629
+ })
630
+
631
+ describe("hide animation timing", () => {
632
+ test("sets data-state to closed immediately", async () => {
633
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
634
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
635
+
636
+ // Show tooltip
637
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
638
+ await wait(250)
639
+
640
+ // Hide tooltip
641
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
642
+ await wait(10) // hideTimeout is 0ms but let event loop process
643
+
644
+ expect(content.dataset.state).toBe("closed")
645
+ })
646
+
647
+ test("sets hidden attribute after 100ms delay", async () => {
648
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
649
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
650
+
651
+ // Show tooltip
652
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
653
+ await wait(250)
654
+
655
+ expect(content.hidden).toBe(false)
656
+
657
+ // Hide tooltip
658
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
659
+ await wait(10)
660
+
661
+ // Should not be hidden yet
662
+ expect(content.hidden).toBe(false)
663
+
664
+ // After 100ms animation delay
665
+ await wait(100)
666
+
667
+ expect(content.hidden).toBe(true)
668
+ })
669
+ })
670
+
671
+ describe("integration scenarios", () => {
672
+ test("can show and hide tooltip multiple times", async () => {
673
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
674
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
675
+
676
+ // Show
677
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
678
+ await wait(250)
679
+ expect(content.hidden).toBe(false)
680
+
681
+ // Hide
682
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
683
+ await wait(150)
684
+ expect(content.hidden).toBe(true)
685
+
686
+ // Show again
687
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
688
+ await wait(250)
689
+ expect(content.hidden).toBe(false)
690
+
691
+ // Hide again
692
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
693
+ await wait(150)
694
+ expect(content.hidden).toBe(true)
695
+ })
696
+
697
+ test("switches between hover and focus correctly", async () => {
698
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
699
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
700
+
701
+ // Show on hover
702
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
703
+ await wait(250)
704
+ expect(content.hidden).toBe(false)
705
+
706
+ // Hide on leave
707
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
708
+ await wait(150)
709
+ expect(content.hidden).toBe(true)
710
+
711
+ // Show on focus
712
+ trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }))
713
+ await wait(250)
714
+ expect(content.hidden).toBe(false)
715
+
716
+ // Hide on blur
717
+ trigger.dispatchEvent(new FocusEvent('blur', { bubbles: true }))
718
+ await wait(150)
719
+ expect(content.hidden).toBe(true)
720
+ })
721
+
722
+ test("repositions tooltip on each show", async () => {
723
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
724
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
725
+
726
+ // Show tooltip
727
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
728
+ await wait(250)
729
+
730
+ const firstPosition = {
731
+ bottom: content.style.bottom,
732
+ left: content.style.left,
733
+ transform: content.style.transform
734
+ }
735
+
736
+ // Hide
737
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
738
+ await wait(150)
739
+
740
+ // Show again
741
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
742
+ await wait(250)
743
+
744
+ // Position should be recalculated (same values in this case)
745
+ expect(content.style.bottom).toBe(firstPosition.bottom)
746
+ expect(content.style.left).toBe(firstPosition.left)
747
+ expect(content.style.transform).toBe(firstPosition.transform)
748
+ })
749
+
750
+ test("handles rapid hover in and out gracefully", async () => {
751
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
752
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
753
+
754
+ // Rapid hover events
755
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
756
+ await wait(50)
757
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
758
+ await wait(20)
759
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
760
+ await wait(50)
761
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
762
+ await wait(20)
763
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
764
+
765
+ // Complete the final show
766
+ await wait(250)
767
+
768
+ expect(content.hidden).toBe(false)
769
+ expect(content.dataset.state).toBe("open")
770
+ })
771
+ })
772
+
773
+ describe("cleanup on disconnect", () => {
774
+ test("prevents show after disconnect", async () => {
775
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
776
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
777
+
778
+ // Start showing
779
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
780
+
781
+ // Disconnect before delay completes
782
+ controller.disconnect()
783
+
784
+ // Advance past delay
785
+ await wait(300)
786
+
787
+ // Should not show because timeouts were cleared
788
+ expect(content.hidden).toBe(true)
789
+ })
790
+
791
+ test("clears pending hide timeout on disconnect", async () => {
792
+ const trigger = element.querySelector('[data-shadcn--tooltip-target="trigger"]')
793
+ const content = element.querySelector('[data-shadcn--tooltip-target="content"]')
794
+
795
+ // Show tooltip
796
+ trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
797
+ await wait(250)
798
+
799
+ // Start hiding
800
+ trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }))
801
+
802
+ // Disconnect before hide completes
803
+ controller.disconnect()
804
+
805
+ expect(controller.hideTimeout).toBeNull()
806
+ })
807
+ })
808
+ })