@getmicdrop/svelte-components 5.18.0 → 5.19.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 (476) hide show
  1. package/dist/calendar/AboutShow/AboutShow.svelte +191 -187
  2. package/dist/calendar/AboutShow/AboutShow.svelte.d.ts +2 -2
  3. package/dist/calendar/AboutShow/AboutShow.svelte.d.ts.map +1 -1
  4. package/dist/calendar/Calendar/MiniMonthCalendar.svelte +801 -780
  5. package/dist/calendar/Calendar/MiniMonthCalendar.svelte.d.ts +4 -0
  6. package/dist/calendar/Calendar/MiniMonthCalendar.svelte.d.ts.map +1 -1
  7. package/dist/calendar/FAQs/FAQs.svelte +88 -77
  8. package/dist/calendar/FAQs/FAQs.svelte.d.ts +6 -4
  9. package/dist/calendar/FAQs/FAQs.svelte.d.ts.map +1 -1
  10. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +140 -125
  11. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte.d.ts +2 -0
  12. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte.d.ts.map +1 -1
  13. package/dist/calendar/OrderSummary/OrderSummary.spec.js +3 -3
  14. package/dist/calendar/OrderSummary/OrderSummary.svelte +461 -457
  15. package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts +4 -2
  16. package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts.map +1 -1
  17. package/dist/calendar/PublicCard/PublicCard.svelte +164 -155
  18. package/dist/calendar/PublicCard/PublicCard.svelte.d.ts +4 -0
  19. package/dist/calendar/PublicCard/PublicCard.svelte.d.ts.map +1 -1
  20. package/dist/calendar/ShowCard/ShowCard.svelte +180 -166
  21. package/dist/calendar/ShowCard/ShowCard.svelte.d.ts +4 -0
  22. package/dist/calendar/ShowCard/ShowCard.svelte.d.ts.map +1 -1
  23. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +80 -72
  24. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts +4 -0
  25. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte.d.ts.map +1 -1
  26. package/dist/calendar/index.js +15 -15
  27. package/dist/components/Heading.spec.js +89 -89
  28. package/dist/components/Heading.svelte +60 -60
  29. package/dist/components/Layout/AppShell.svelte +104 -104
  30. package/dist/components/Layout/ContentSection.svelte +80 -80
  31. package/dist/components/Layout/Grid.svelte +101 -101
  32. package/dist/components/Layout/Heading.svelte +81 -81
  33. package/dist/components/Layout/PageContainer.svelte +69 -69
  34. package/dist/components/Layout/Responsive.svelte +75 -75
  35. package/dist/components/Layout/Section.svelte +80 -80
  36. package/dist/components/Layout/ShowOnDesktop.svelte +37 -37
  37. package/dist/components/Layout/ShowOnMobile.svelte +37 -37
  38. package/dist/components/Layout/Sidebar.svelte +108 -108
  39. package/dist/components/Layout/Stack.spec.js +1 -1
  40. package/dist/components/Layout/Stack.svelte +52 -52
  41. package/dist/components/Layout/Text.svelte +87 -87
  42. package/dist/components/Layout/TwoColumn.svelte +108 -108
  43. package/dist/components/Text.spec.js +89 -89
  44. package/dist/components/Text.svelte +53 -53
  45. package/dist/config.js +151 -151
  46. package/dist/config.spec.js +29 -29
  47. package/dist/constants/formOptions.js +48 -48
  48. package/dist/constants/validation.js +91 -91
  49. package/dist/constants/validation.spec.js +64 -64
  50. package/dist/datetime/README.md +323 -323
  51. package/dist/datetime/__tests__/format.test.js +1 -1
  52. package/dist/datetime/__tests__/parse.test.js +1 -1
  53. package/dist/datetime/__tests__/timezone.test.js +1 -1
  54. package/dist/datetime/parse.js +1 -1
  55. package/dist/forms/createFormStore.svelte.js +1 -0
  56. package/dist/forms/createFormStore.svelte.spec.js +1 -0
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +66 -57
  60. package/dist/index.spec.js +369 -369
  61. package/dist/patterns/chat/ChatActivityNotice.spec.js +59 -59
  62. package/dist/patterns/chat/ChatActivityNotice.svelte +41 -41
  63. package/dist/patterns/chat/ChatBubble.spec.js +91 -91
  64. package/dist/patterns/chat/ChatBubble.svelte +103 -95
  65. package/dist/patterns/chat/ChatBubble.svelte.d.ts +6 -4
  66. package/dist/patterns/chat/ChatBubble.svelte.d.ts.map +1 -1
  67. package/dist/patterns/chat/ChatContainer.spec.js +30 -30
  68. package/dist/patterns/chat/ChatContainer.svelte +46 -46
  69. package/dist/patterns/chat/ChatDateDivider.spec.js +30 -30
  70. package/dist/patterns/chat/ChatDateDivider.svelte +27 -27
  71. package/dist/patterns/chat/ChatInvitationBubble.spec.js +46 -46
  72. package/dist/patterns/chat/ChatInvitationBubble.svelte +46 -37
  73. package/dist/patterns/chat/ChatInvitationBubble.svelte.d.ts +6 -3
  74. package/dist/patterns/chat/ChatInvitationBubble.svelte.d.ts.map +1 -1
  75. package/dist/patterns/chat/ChatInvitationNotice.spec.js +32 -32
  76. package/dist/patterns/chat/ChatInvitationNotice.svelte +36 -27
  77. package/dist/patterns/chat/ChatInvitationNotice.svelte.d.ts +6 -3
  78. package/dist/patterns/chat/ChatInvitationNotice.svelte.d.ts.map +1 -1
  79. package/dist/patterns/chat/ChatMessageGroup.spec.js +58 -58
  80. package/dist/patterns/chat/ChatMessageGroup.svelte +57 -57
  81. package/dist/patterns/chat/ChatSlotUpdate.spec.js +65 -65
  82. package/dist/patterns/chat/ChatSlotUpdate.svelte +46 -46
  83. package/dist/patterns/chat/ChatStatusBadge.spec.js +79 -79
  84. package/dist/patterns/chat/ChatStatusBadge.svelte +91 -91
  85. package/dist/patterns/chat/ChatStatusTransition.spec.js +81 -81
  86. package/dist/patterns/chat/ChatStatusTransition.svelte +64 -64
  87. package/dist/patterns/chat/ChatTextBubble.spec.js +35 -35
  88. package/dist/patterns/chat/ChatTextBubble.svelte +41 -41
  89. package/dist/patterns/chat/index.js +22 -22
  90. package/dist/patterns/data/DataGrid.svelte +45 -45
  91. package/dist/patterns/data/DataList.svelte +24 -24
  92. package/dist/patterns/data/DataTable.svelte +45 -45
  93. package/dist/patterns/data/index.js +4 -4
  94. package/dist/patterns/forms/FormActions.spec.js +95 -95
  95. package/dist/patterns/forms/FormActions.stories.svelte +97 -97
  96. package/dist/patterns/forms/FormActions.svelte +46 -46
  97. package/dist/patterns/forms/FormGrid.svelte +33 -33
  98. package/dist/patterns/forms/FormSection.svelte +32 -32
  99. package/dist/patterns/forms/FormValidationSummary.stories.svelte +97 -97
  100. package/dist/patterns/forms/FormValidationSummary.svelte +82 -74
  101. package/dist/patterns/forms/FormValidationSummary.svelte.d.ts +5 -3
  102. package/dist/patterns/forms/FormValidationSummary.svelte.d.ts.map +1 -1
  103. package/dist/patterns/forms/index.js +5 -5
  104. package/dist/patterns/index.js +21 -21
  105. package/dist/patterns/layout/Sidebar.svelte +39 -39
  106. package/dist/patterns/layout/SidebarTestWrapper.svelte +34 -34
  107. package/dist/patterns/layout/SidebarTestWrapper.svelte.d.ts +1 -1
  108. package/dist/patterns/layout/Stack.svelte +61 -61
  109. package/dist/patterns/layout/index.js +29 -29
  110. package/dist/patterns/navigation/BottomNav.stories.svelte +117 -117
  111. package/dist/patterns/navigation/BottomNav.svelte +82 -74
  112. package/dist/patterns/navigation/BottomNav.svelte.d.ts +10 -9
  113. package/dist/patterns/navigation/BottomNav.svelte.d.ts.map +1 -1
  114. package/dist/patterns/navigation/Header.stories.svelte +77 -77
  115. package/dist/patterns/navigation/Header.svelte +263 -251
  116. package/dist/patterns/navigation/Header.svelte.d.ts +17 -9
  117. package/dist/patterns/navigation/Header.svelte.d.ts.map +1 -1
  118. package/dist/patterns/navigation/index.js +3 -3
  119. package/dist/patterns/page/PageHeader.svelte +49 -49
  120. package/dist/patterns/page/PageLayout.svelte +40 -40
  121. package/dist/patterns/page/PageLoader.spec.js +57 -57
  122. package/dist/patterns/page/PageLoader.stories.svelte +137 -137
  123. package/dist/patterns/page/PageLoader.svelte +62 -53
  124. package/dist/patterns/page/PageLoader.svelte.d.ts +6 -3
  125. package/dist/patterns/page/PageLoader.svelte.d.ts.map +1 -1
  126. package/dist/patterns/page/SectionHeader.svelte +51 -51
  127. package/dist/patterns/page/index.js +5 -5
  128. package/dist/presets/badges.js +112 -112
  129. package/dist/presets/buttons.js +76 -76
  130. package/dist/presets/index.js +9 -9
  131. package/dist/primitives/Accordion/Accordion.stories.svelte +75 -75
  132. package/dist/primitives/Accordion/Accordion.svelte +62 -62
  133. package/dist/primitives/Accordion/AccordionItem.svelte +103 -95
  134. package/dist/primitives/Accordion/AccordionItem.svelte.d.ts +6 -4
  135. package/dist/primitives/Accordion/AccordionItem.svelte.d.ts.map +1 -1
  136. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte +107 -107
  137. package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte +28 -28
  138. package/dist/primitives/Alert/Alert.spec.js +173 -173
  139. package/dist/primitives/Alert/Alert.stories.svelte +88 -88
  140. package/dist/primitives/Alert/Alert.svelte +72 -64
  141. package/dist/primitives/Alert/Alert.svelte.d.ts +6 -4
  142. package/dist/primitives/Alert/Alert.svelte.d.ts.map +1 -1
  143. package/dist/primitives/Avatar/Avatar.stories.svelte +94 -94
  144. package/dist/primitives/Avatar/Avatar.svelte +66 -66
  145. package/dist/primitives/AvatarButton/AvatarButton.svelte +57 -57
  146. package/dist/primitives/Badges/Badge.spec.js +144 -144
  147. package/dist/primitives/Badges/Badge.stories.svelte +86 -86
  148. package/dist/primitives/Badges/Badge.svelte +99 -99
  149. package/dist/primitives/BottomSheet/BottomSheet.spec.js +238 -238
  150. package/dist/primitives/BottomSheet/BottomSheet.stories.svelte +83 -83
  151. package/dist/primitives/BottomSheet/BottomSheet.svelte +115 -115
  152. package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte +20 -20
  153. package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +123 -123
  154. package/dist/primitives/Breadcrumb/Breadcrumb.stories.svelte +23 -23
  155. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +107 -99
  156. package/dist/primitives/Breadcrumb/Breadcrumb.svelte.d.ts +13 -9
  157. package/dist/primitives/Breadcrumb/Breadcrumb.svelte.d.ts.map +1 -1
  158. package/dist/primitives/Button/Button.spec.js +225 -225
  159. package/dist/primitives/Button/Button.stories.svelte +76 -76
  160. package/dist/primitives/Button/Button.svelte +278 -278
  161. package/dist/primitives/Button/ButtonGroup.spec.js +44 -44
  162. package/dist/primitives/Button/ButtonGroup.svelte +50 -50
  163. package/dist/primitives/Button/ButtonSaveDemo.spec.js +146 -146
  164. package/dist/primitives/Button/ButtonSaveDemo.svelte +25 -25
  165. package/dist/primitives/Button/ButtonVariantShowcase.svelte +129 -129
  166. package/dist/primitives/Card.spec.js +49 -49
  167. package/dist/primitives/Card.stories.svelte +22 -22
  168. package/dist/primitives/Card.svelte +28 -28
  169. package/dist/primitives/CardAction/CardAction.svelte +68 -68
  170. package/dist/primitives/Checkbox/Checkbox.stories.svelte +84 -84
  171. package/dist/primitives/Checkbox/Checkbox.svelte +88 -88
  172. package/dist/primitives/DarkModeToggle.spec.js +390 -390
  173. package/dist/primitives/DarkModeToggle.stories.svelte +57 -57
  174. package/dist/primitives/DarkModeToggle.svelte +147 -136
  175. package/dist/primitives/DarkModeToggle.svelte.d.ts +9 -3
  176. package/dist/primitives/DarkModeToggle.svelte.d.ts.map +1 -1
  177. package/dist/primitives/Drawer/Drawer.stories.svelte +100 -100
  178. package/dist/primitives/Drawer/Drawer.svelte +224 -224
  179. package/dist/primitives/Drawer/DrawerTestWrapper.svelte +86 -86
  180. package/dist/primitives/Dropdown/Dropdown.stories.svelte +137 -137
  181. package/dist/primitives/Dropdown/Dropdown.svelte +179 -170
  182. package/dist/primitives/Dropdown/Dropdown.svelte.d.ts +7 -4
  183. package/dist/primitives/Dropdown/Dropdown.svelte.d.ts.map +1 -1
  184. package/dist/primitives/Dropdown/DropdownDivider.spec.js +30 -30
  185. package/dist/primitives/Dropdown/DropdownDivider.svelte +9 -9
  186. package/dist/primitives/Dropdown/DropdownItem.svelte +80 -80
  187. package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte +43 -43
  188. package/dist/primitives/Helper/Helper.spec.js +57 -57
  189. package/dist/primitives/Helper/Helper.svelte +33 -33
  190. package/dist/primitives/Icons/ArrowLeft.svelte +21 -21
  191. package/dist/primitives/Icons/ArrowRight.svelte +21 -21
  192. package/dist/primitives/Icons/Availability.svelte +27 -27
  193. package/dist/primitives/Icons/Back.svelte +27 -27
  194. package/dist/primitives/Icons/CheckCircle.svelte +19 -19
  195. package/dist/primitives/Icons/CheckCircleOutline.svelte +28 -28
  196. package/dist/primitives/Icons/ChevronLeft.svelte +17 -17
  197. package/dist/primitives/Icons/ChevronRight.svelte +17 -17
  198. package/dist/primitives/Icons/Copy.svelte +28 -28
  199. package/dist/primitives/Icons/Cross.svelte +18 -18
  200. package/dist/primitives/Icons/DownArrow.svelte +21 -21
  201. package/dist/primitives/Icons/ErrorCircle.svelte +19 -19
  202. package/dist/primitives/Icons/FacebookIcon.svelte +15 -15
  203. package/dist/primitives/Icons/Home.svelte +28 -28
  204. package/dist/primitives/Icons/Icon.spec.js +175 -175
  205. package/dist/primitives/Icons/Icon.stories.svelte +100 -100
  206. package/dist/primitives/Icons/Icon.svelte +79 -79
  207. package/dist/primitives/Icons/IconGallery.stories.svelte +235 -235
  208. package/dist/primitives/Icons/ImageOutline.svelte +21 -21
  209. package/dist/primitives/Icons/Info.svelte +20 -20
  210. package/dist/primitives/Icons/InstagramIcon.svelte +21 -21
  211. package/dist/primitives/Icons/LogoInstagram.svelte +15 -15
  212. package/dist/primitives/Icons/Message.svelte +28 -28
  213. package/dist/primitives/Icons/MoonIcon.svelte +18 -18
  214. package/dist/primitives/Icons/More.svelte +34 -34
  215. package/dist/primitives/Icons/MoreHori.spec.js +67 -67
  216. package/dist/primitives/Icons/MoreHori.svelte +35 -35
  217. package/dist/primitives/Icons/Notification.svelte +27 -27
  218. package/dist/primitives/Icons/Payment.svelte +27 -27
  219. package/dist/primitives/Icons/Profile.svelte +34 -34
  220. package/dist/primitives/Icons/Reload.svelte +42 -42
  221. package/dist/primitives/Icons/Shows.svelte +34 -34
  222. package/dist/primitives/Icons/Signout.svelte +34 -34
  223. package/dist/primitives/Icons/SunIcon.svelte +21 -21
  224. package/dist/primitives/Icons/TiktokIcon.svelte +15 -15
  225. package/dist/primitives/Icons/TrashBinOutline.svelte +21 -21
  226. package/dist/primitives/Icons/TwitterIcon.svelte +15 -15
  227. package/dist/primitives/Icons/WarningIcon.spec.js +30 -30
  228. package/dist/primitives/Icons/WarningIcon.svelte +24 -24
  229. package/dist/primitives/Input/Input.spec.js +1237 -1237
  230. package/dist/primitives/Input/Input.stories.svelte +139 -139
  231. package/dist/primitives/Input/Input.svelte +444 -431
  232. package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
  233. package/dist/primitives/Input/Select.spec.js +632 -632
  234. package/dist/primitives/Input/Select.stories.svelte +112 -112
  235. package/dist/primitives/Input/Select.svelte +252 -252
  236. package/dist/primitives/Input/Textarea.stories.svelte +137 -137
  237. package/dist/primitives/Input/Textarea.svelte +105 -105
  238. package/dist/primitives/Label/Label.svelte +37 -37
  239. package/dist/primitives/LandingButton/LandingButton.spec.js +61 -61
  240. package/dist/primitives/LandingButton/LandingButton.svelte +92 -92
  241. package/dist/primitives/MenuItem/MenuItem.spec.js +130 -130
  242. package/dist/primitives/MenuItem/MenuItem.svelte +85 -85
  243. package/dist/primitives/Modal/Modal.spec.js +314 -314
  244. package/dist/primitives/Modal/Modal.stories.svelte +86 -86
  245. package/dist/primitives/Modal/Modal.svelte +181 -181
  246. package/dist/primitives/NavItem/NavItem.spec.js +97 -97
  247. package/dist/primitives/NavItem/NavItem.svelte +75 -75
  248. package/dist/primitives/NumberInput/NumberInput.svelte +113 -105
  249. package/dist/primitives/NumberInput/NumberInput.svelte.d.ts.map +1 -1
  250. package/dist/primitives/Pagination/Pagination.stories.svelte +76 -76
  251. package/dist/primitives/Pagination/Pagination.svelte +275 -261
  252. package/dist/primitives/Pagination/Pagination.svelte.d.ts +14 -6
  253. package/dist/primitives/Pagination/Pagination.svelte.d.ts.map +1 -1
  254. package/dist/primitives/Radio/Radio.stories.svelte +80 -80
  255. package/dist/primitives/Radio/Radio.svelte +67 -67
  256. package/dist/primitives/SearchResultItem/SearchResultItem.spec.js +78 -78
  257. package/dist/primitives/SearchResultItem/SearchResultItem.svelte +109 -109
  258. package/dist/primitives/SidebarToggle/SidebarToggle.spec.js +61 -61
  259. package/dist/primitives/SidebarToggle/SidebarToggle.svelte +55 -55
  260. package/dist/primitives/Skeleton/CardPlaceholder.svelte +96 -87
  261. package/dist/primitives/Skeleton/CardPlaceholder.svelte.d.ts +8 -5
  262. package/dist/primitives/Skeleton/CardPlaceholder.svelte.d.ts.map +1 -1
  263. package/dist/primitives/Skeleton/ImagePlaceholder.svelte +68 -59
  264. package/dist/primitives/Skeleton/ImagePlaceholder.svelte.d.ts +8 -5
  265. package/dist/primitives/Skeleton/ImagePlaceholder.svelte.d.ts.map +1 -1
  266. package/dist/primitives/Skeleton/ListPlaceholder.svelte +85 -76
  267. package/dist/primitives/Skeleton/ListPlaceholder.svelte.d.ts +8 -5
  268. package/dist/primitives/Skeleton/ListPlaceholder.svelte.d.ts.map +1 -1
  269. package/dist/primitives/Skeleton/Skeleton.stories.svelte +151 -151
  270. package/dist/primitives/Skeleton/Skeleton.svelte +55 -46
  271. package/dist/primitives/Skeleton/Skeleton.svelte.d.ts +7 -4
  272. package/dist/primitives/Skeleton/Skeleton.svelte.d.ts.map +1 -1
  273. package/dist/primitives/Spinner/Spinner.spec.js +84 -84
  274. package/dist/primitives/Spinner/Spinner.stories.svelte +29 -29
  275. package/dist/primitives/Spinner/Spinner.svelte +52 -44
  276. package/dist/primitives/Spinner/Spinner.svelte.d.ts +6 -4
  277. package/dist/primitives/Spinner/Spinner.svelte.d.ts.map +1 -1
  278. package/dist/primitives/Tabs/TabItem.svelte +52 -52
  279. package/dist/primitives/Tabs/Tabs.stories.svelte +112 -112
  280. package/dist/primitives/Tabs/Tabs.svelte +137 -137
  281. package/dist/primitives/Toggle.spec.js +221 -221
  282. package/dist/primitives/Toggle.stories.svelte +92 -92
  283. package/dist/primitives/Toggle.svelte +141 -141
  284. package/dist/primitives/ToggleTestWrapper.svelte +30 -30
  285. package/dist/primitives/Tooltip/Tooltip.spec.js +126 -126
  286. package/dist/primitives/Tooltip/Tooltip.svelte +83 -83
  287. package/dist/primitives/Typography/Typography.svelte +53 -53
  288. package/dist/primitives/ValidationError.spec.js +103 -103
  289. package/dist/primitives/ValidationError.stories.svelte +112 -112
  290. package/dist/primitives/ValidationError.svelte +29 -29
  291. package/dist/primitives/index.js +113 -113
  292. package/dist/recipes/CropImage/CropImage.spec.js +208 -208
  293. package/dist/recipes/CropImage/CropImage.stories.svelte +104 -104
  294. package/dist/recipes/CropImage/CropImage.svelte +241 -241
  295. package/dist/recipes/ImageUploader/ImageUploader.stories.svelte +125 -125
  296. package/dist/recipes/ImageUploader/ImageUploader.svelte +994 -992
  297. package/dist/recipes/ImageUploader/ImageUploader.svelte.d.ts +1 -0
  298. package/dist/recipes/ImageUploader/ImageUploader.svelte.d.ts.map +1 -1
  299. package/dist/recipes/SuperLogin/SuperLogin.svelte +25 -23
  300. package/dist/recipes/SuperLogin/SuperLogin.svelte.d.ts.map +1 -1
  301. package/dist/recipes/Toaster/Toaster.stories.svelte +62 -62
  302. package/dist/recipes/feedback/EmptyState/EmptyState.svelte +75 -66
  303. package/dist/recipes/feedback/EmptyState/EmptyState.svelte.d.ts +7 -4
  304. package/dist/recipes/feedback/EmptyState/EmptyState.svelte.d.ts.map +1 -1
  305. package/dist/recipes/feedback/ErrorDisplay.spec.js +69 -69
  306. package/dist/recipes/feedback/ErrorDisplay.stories.svelte +113 -113
  307. package/dist/recipes/feedback/ErrorDisplay.svelte +67 -67
  308. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.spec.js +133 -133
  309. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.svelte +176 -176
  310. package/dist/recipes/feedback/index.js +4 -4
  311. package/dist/recipes/fields/CheckboxField.svelte +85 -85
  312. package/dist/recipes/fields/FormField.svelte +58 -58
  313. package/dist/recipes/fields/RadioGroup.svelte +95 -95
  314. package/dist/recipes/fields/SelectField.svelte +82 -82
  315. package/dist/recipes/fields/TextareaField.svelte +101 -101
  316. package/dist/recipes/fields/ToggleField.svelte +60 -60
  317. package/dist/recipes/fields/index.js +7 -7
  318. package/dist/recipes/index.js +24 -24
  319. package/dist/recipes/inputs/MultiSelect.spec.js +263 -263
  320. package/dist/recipes/inputs/MultiSelect.stories.svelte +133 -133
  321. package/dist/recipes/inputs/MultiSelect.svelte +291 -283
  322. package/dist/recipes/inputs/MultiSelect.svelte.d.ts.map +1 -1
  323. package/dist/recipes/inputs/OTPInput.spec.js +251 -251
  324. package/dist/recipes/inputs/OTPInput.stories.svelte +162 -162
  325. package/dist/recipes/inputs/OTPInput.svelte +128 -117
  326. package/dist/recipes/inputs/OTPInput.svelte.d.ts +14 -12
  327. package/dist/recipes/inputs/OTPInput.svelte.d.ts.map +1 -1
  328. package/dist/recipes/inputs/PasswordInput.svelte +130 -121
  329. package/dist/recipes/inputs/PasswordInput.svelte.d.ts +7 -4
  330. package/dist/recipes/inputs/PasswordInput.svelte.d.ts.map +1 -1
  331. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte +142 -131
  332. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte.d.ts +8 -3
  333. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte.d.ts.map +1 -1
  334. package/dist/recipes/inputs/PhoneInput.svelte +254 -258
  335. package/dist/recipes/inputs/PhoneInput.svelte.d.ts +0 -2
  336. package/dist/recipes/inputs/PhoneInput.svelte.d.ts.map +1 -1
  337. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.stories.svelte +170 -170
  338. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte +349 -336
  339. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte.d.ts +21 -16
  340. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte.d.ts.map +1 -1
  341. package/dist/recipes/inputs/Search.svelte +110 -102
  342. package/dist/recipes/inputs/Search.svelte.d.ts +7 -5
  343. package/dist/recipes/inputs/Search.svelte.d.ts.map +1 -1
  344. package/dist/recipes/inputs/index.js +8 -8
  345. package/dist/recipes/inputs/phoneInput/CountrySelector.svelte +240 -297
  346. package/dist/recipes/inputs/phoneInput/CountrySelector.svelte.d.ts +0 -2
  347. package/dist/recipes/inputs/phoneInput/CountrySelector.svelte.d.ts.map +1 -1
  348. package/dist/recipes/inputs/phoneInput/countryData.d.ts +0 -2
  349. package/dist/recipes/inputs/phoneInput/countryData.d.ts.map +1 -1
  350. package/dist/recipes/inputs/phoneInput/countryData.js +3 -3
  351. package/dist/recipes/modals/AlertModal.svelte +139 -130
  352. package/dist/recipes/modals/AlertModal.svelte.d.ts +4 -2
  353. package/dist/recipes/modals/AlertModal.svelte.d.ts.map +1 -1
  354. package/dist/recipes/modals/ConfirmationModal.spec.js +396 -396
  355. package/dist/recipes/modals/ConfirmationModal.stories.svelte +119 -119
  356. package/dist/recipes/modals/ConfirmationModal.svelte +169 -162
  357. package/dist/recipes/modals/ConfirmationModal.svelte.d.ts +2 -0
  358. package/dist/recipes/modals/ConfirmationModal.svelte.d.ts.map +1 -1
  359. package/dist/recipes/modals/InputModal.svelte +194 -182
  360. package/dist/recipes/modals/InputModal.svelte.d.ts +6 -4
  361. package/dist/recipes/modals/InputModal.svelte.d.ts.map +1 -1
  362. package/dist/recipes/modals/ModalStateManager.spec.js +100 -100
  363. package/dist/recipes/modals/ModalStateManager.svelte +77 -77
  364. package/dist/recipes/modals/ModalTestWrapper.svelte +65 -65
  365. package/dist/recipes/modals/StatusModal.svelte +216 -206
  366. package/dist/recipes/modals/StatusModal.svelte.d.ts +4 -2
  367. package/dist/recipes/modals/StatusModal.svelte.d.ts.map +1 -1
  368. package/dist/recipes/modals/index.js +7 -7
  369. package/dist/schemas/common.js +1 -1
  370. package/dist/schemas/event.d.ts +4 -4
  371. package/dist/schemas/event.js +1 -1
  372. package/dist/schemas/order.d.ts +2 -2
  373. package/dist/schemas/order.js +1 -0
  374. package/dist/schemas/performer.js +1 -1
  375. package/dist/schemas/promo.d.ts +4 -4
  376. package/dist/schemas/promo.js +2 -2
  377. package/dist/schemas/ticket.js +1 -1
  378. package/dist/schemas/user.js +2 -1
  379. package/dist/services/event.service.d.ts +11 -0
  380. package/dist/services/event.service.d.ts.map +1 -0
  381. package/dist/services/event.service.js +64 -0
  382. package/dist/services/event.service.spec.d.ts +2 -0
  383. package/dist/services/event.service.spec.d.ts.map +1 -0
  384. package/dist/services/event.service.spec.js +168 -0
  385. package/dist/services/{ShowService.d.ts → show.service.d.ts} +47 -47
  386. package/dist/services/show.service.d.ts.map +1 -0
  387. package/dist/services/show.service.js +115 -0
  388. package/dist/services/show.service.spec.d.ts +2 -0
  389. package/dist/services/show.service.spec.d.ts.map +1 -0
  390. package/dist/services/show.service.spec.js +242 -0
  391. package/dist/stores/auth.d.ts +8 -8
  392. package/dist/stores/auth.svelte.spec.js +1 -1
  393. package/dist/stores/index.js +9 -9
  394. package/dist/stores/toaster.d.ts +3 -3
  395. package/dist/stores/toaster.js +13 -13
  396. package/dist/stores/toaster.spec.js +59 -59
  397. package/dist/stories/ButtonAuditReview.stories.svelte +14 -14
  398. package/dist/stories/ButtonAuditReview.svelte +427 -427
  399. package/dist/stories/PatternsGallery.stories.svelte +19 -19
  400. package/dist/stories/PatternsGallery.svelte +399 -399
  401. package/dist/stories/PrimitivesGallery.stories.svelte +19 -19
  402. package/dist/stories/PrimitivesGallery.svelte +756 -756
  403. package/dist/stories/RecipesGallery.stories.svelte +19 -19
  404. package/dist/stories/RecipesGallery.svelte +454 -454
  405. package/dist/stories/button-audit-manifest.json +11186 -11186
  406. package/dist/tailwind/preset.cjs +82 -82
  407. package/dist/telemetry.js +402 -402
  408. package/dist/telemetry.server.js +212 -212
  409. package/dist/telemetry.server.spec.js +437 -437
  410. package/dist/telemetry.spec.js +1173 -1173
  411. package/dist/tokens/tokens.css +87 -87
  412. package/dist/tokens/typography-base.css +163 -163
  413. package/dist/tokens/utilities.css +353 -353
  414. package/dist/utils/__tests__/auth.test.js +431 -431
  415. package/dist/utils/apiConfig.d.ts +29 -29
  416. package/dist/utils/apiConfig.js +117 -117
  417. package/dist/utils/apiConfig.spec.js +219 -219
  418. package/dist/utils/assets.d.ts +3 -0
  419. package/dist/utils/assets.d.ts.map +1 -0
  420. package/dist/utils/assets.js +3 -0
  421. package/dist/utils/auth.d.ts +46 -46
  422. package/dist/utils/auth.js +195 -195
  423. package/dist/utils/classNames.d.ts +10 -0
  424. package/dist/utils/classNames.d.ts.map +1 -0
  425. package/dist/utils/classNames.js +15 -0
  426. package/dist/utils/cookieHelpers.d.ts +40 -0
  427. package/dist/utils/cookieHelpers.d.ts.map +1 -0
  428. package/dist/utils/cookieHelpers.js +102 -0
  429. package/dist/utils/dateHelpers.d.ts +71 -0
  430. package/dist/utils/dateHelpers.d.ts.map +1 -0
  431. package/dist/utils/dateHelpers.js +253 -0
  432. package/dist/utils/eventFormatters.d.ts +9 -0
  433. package/dist/utils/eventFormatters.d.ts.map +1 -0
  434. package/dist/utils/eventFormatters.js +96 -0
  435. package/dist/utils/fetchHelpers.d.ts +17 -0
  436. package/dist/utils/fetchHelpers.d.ts.map +1 -0
  437. package/dist/utils/fetchHelpers.js +44 -0
  438. package/dist/utils/formatters.d.ts +62 -0
  439. package/dist/utils/formatters.d.ts.map +1 -1
  440. package/dist/utils/formatters.js +128 -0
  441. package/dist/utils/formatters.spec.js +128 -1
  442. package/dist/utils/greetings.js +187 -187
  443. package/dist/utils/greetings.spec.js +337 -337
  444. package/dist/utils/haptic.spec.js +1 -1
  445. package/dist/utils/imageValidation.js +121 -121
  446. package/dist/utils/imageValidation.spec.js +223 -223
  447. package/dist/utils/permissions.d.ts +9 -0
  448. package/dist/utils/permissions.d.ts.map +1 -0
  449. package/dist/utils/permissions.js +93 -0
  450. package/dist/utils/portal.d.ts +11 -11
  451. package/dist/utils/portal.js +25 -25
  452. package/dist/utils/portal.spec.js +143 -143
  453. package/dist/utils/stringHelpers.d.ts +17 -0
  454. package/dist/utils/stringHelpers.d.ts.map +1 -0
  455. package/dist/utils/stringHelpers.js +38 -0
  456. package/dist/utils/transitions.js +4 -4
  457. package/dist/utils/utils/utils.d.ts +2 -73
  458. package/dist/utils/utils/utils.d.ts.map +1 -1
  459. package/dist/utils/utils/utils.js +3 -3
  460. package/dist/utils/utils/utils.spec.js +698 -698
  461. package/dist/utils/utils.d.ts +41 -98
  462. package/dist/utils/utils.d.ts.map +1 -1
  463. package/dist/utils/utils.js +59 -693
  464. package/dist/utils/utils.spec.js +643 -643
  465. package/package.json +301 -297
  466. package/dist/services/EventService.d.ts +0 -5
  467. package/dist/services/EventService.d.ts.map +0 -1
  468. package/dist/services/EventService.js +0 -79
  469. package/dist/services/EventService.spec.d.ts +0 -2
  470. package/dist/services/EventService.spec.d.ts.map +0 -1
  471. package/dist/services/EventService.spec.js +0 -217
  472. package/dist/services/ShowService.d.ts.map +0 -1
  473. package/dist/services/ShowService.js +0 -144
  474. package/dist/services/ShowService.spec.d.ts +0 -2
  475. package/dist/services/ShowService.spec.d.ts.map +0 -1
  476. package/dist/services/ShowService.spec.js +0 -345
@@ -1,632 +1,632 @@
1
- import { render, screen, fireEvent } from "@testing-library/svelte";
2
- import userEvent from "@testing-library/user-event";
3
- import { expect, describe, test, vi } from "vitest";
4
- import Select from "./Select.svelte";
5
-
6
- // Mock transitions to be instant for testing
7
- vi.mock('../../utils/transitions.js', () => ({
8
- bloom: () => ({ duration: 0, delay: 0, css: () => '' }),
9
- safeSlide: () => ({ duration: 0, delay: 0, css: () => '' }),
10
- }));
11
-
12
- const sampleItems = [
13
- { name: "Option 1", value: "opt1" },
14
- { name: "Option 2", value: "opt2" },
15
- { name: "Option 3", value: "opt3" },
16
- ];
17
-
18
- function setupTest(args = {}) {
19
- const user = userEvent.setup();
20
- const { component } = render(Select, {
21
- props: {
22
- items: sampleItems,
23
- ...args,
24
- },
25
- });
26
- return { user, component };
27
- }
28
-
29
- describe("Select Component Tests", () => {
30
- test("Renders select with label", () => {
31
- setupTest({
32
- label: "Test Label",
33
- id: "test-select",
34
- });
35
- expect(screen.getByText("Test Label")).toBeInTheDocument();
36
- });
37
-
38
- test("Shows placeholder when no value selected", () => {
39
- setupTest({
40
- placeholder: "Select an option",
41
- });
42
- expect(screen.getByText("Select an option")).toBeInTheDocument();
43
- });
44
-
45
- test("Opens dropdown on click", async () => {
46
- const { user } = setupTest();
47
- const trigger = screen.getByRole("button");
48
-
49
- await user.click(trigger);
50
-
51
- expect(screen.getByRole("listbox")).toBeInTheDocument();
52
- expect(screen.getByText("Option 1")).toBeInTheDocument();
53
- expect(screen.getByText("Option 2")).toBeInTheDocument();
54
- expect(screen.getByText("Option 3")).toBeInTheDocument();
55
- });
56
-
57
- test("Selects item on click", async () => {
58
- const { user } = setupTest();
59
- const trigger = screen.getByRole("button");
60
-
61
- await user.click(trigger);
62
- await user.click(screen.getByText("Option 2"));
63
-
64
- expect(screen.getByText("Option 2")).toBeInTheDocument();
65
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
66
- });
67
-
68
- test("Closes dropdown after selection", async () => {
69
- const { user } = setupTest();
70
- const trigger = screen.getByRole("button");
71
-
72
- await user.click(trigger);
73
- await user.click(screen.getByText("Option 1"));
74
-
75
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
76
- });
77
-
78
- test("Shows selected value", () => {
79
- setupTest({
80
- value: "opt2",
81
- });
82
- expect(screen.getByText("Option 2")).toBeInTheDocument();
83
- });
84
-
85
- test("Dispatches change event on selection", async () => {
86
- const changeSpy = vi.fn();
87
- const { user } = setupTest({
88
- onchange: changeSpy,
89
- });
90
-
91
- const trigger = screen.getByRole("button");
92
- await user.click(trigger);
93
- await user.click(screen.getByText("Option 3"));
94
-
95
- expect(changeSpy).toHaveBeenCalled();
96
- expect(changeSpy.mock.calls[0][0]).toEqual({
97
- value: "opt3",
98
- item: { name: "Option 3", value: "opt3" },
99
- });
100
- });
101
-
102
- test("Shows required indicator when required", () => {
103
- setupTest({
104
- label: "Required Field",
105
- required: true,
106
- });
107
- expect(screen.getByText("*")).toBeInTheDocument();
108
- });
109
-
110
- test("Displays error state", () => {
111
- setupTest({
112
- error: "This field is required",
113
- });
114
- expect(screen.getByText("This field is required")).toBeInTheDocument();
115
- });
116
-
117
- test("Disables select when disabled prop is true", async () => {
118
- const { user } = setupTest({
119
- disabled: true,
120
- });
121
- const trigger = screen.getByRole("button");
122
-
123
- expect(trigger).toBeDisabled();
124
- await user.click(trigger);
125
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
126
- });
127
-
128
- test("Navigates options with keyboard", async () => {
129
- const { user } = setupTest();
130
- const trigger = screen.getByRole("button");
131
-
132
- await user.click(trigger);
133
- await user.keyboard("{ArrowDown}");
134
- await user.keyboard("{ArrowDown}");
135
- await user.keyboard("{Enter}");
136
-
137
- // Second option should be selected (started at 0, moved down twice)
138
- expect(screen.getByText("Option 2")).toBeInTheDocument();
139
- });
140
-
141
- test("Closes dropdown on Escape key", async () => {
142
- const { user } = setupTest();
143
- const trigger = screen.getByRole("button");
144
-
145
- await user.click(trigger);
146
- expect(screen.getByRole("listbox")).toBeInTheDocument();
147
-
148
- await user.keyboard("{Escape}");
149
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
150
- });
151
-
152
- test("Opens dropdown with Enter key", async () => {
153
- const { user } = setupTest();
154
- const trigger = screen.getByRole("button");
155
-
156
- trigger.focus();
157
- await user.keyboard("{Enter}");
158
-
159
- expect(screen.getByRole("listbox")).toBeInTheDocument();
160
- });
161
-
162
- test("Opens dropdown with Space key", async () => {
163
- const { user } = setupTest();
164
- const trigger = screen.getByRole("button");
165
-
166
- trigger.focus();
167
- await user.keyboard(" ");
168
-
169
- expect(screen.getByRole("listbox")).toBeInTheDocument();
170
- });
171
-
172
- test("Applies focus ring on focus", async () => {
173
- const { user } = setupTest();
174
- const trigger = screen.getByRole("button");
175
- expect(trigger).toHaveClass("focus:ring-4");
176
- expect(trigger).toHaveClass("focus:ring-blue-300");
177
- });
178
-
179
- test("Closes dropdown when clicking outside", async () => {
180
- const { user } = setupTest();
181
- const trigger = screen.getByRole("button");
182
-
183
- await user.click(trigger);
184
- expect(screen.getByRole("listbox")).toBeInTheDocument();
185
-
186
- // Click outside by clicking on body
187
- await user.click(document.body);
188
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
189
- });
190
-
191
- test("Marks selected option with aria-selected", async () => {
192
- const { user } = setupTest({
193
- value: "opt1",
194
- });
195
- const trigger = screen.getByRole("button");
196
-
197
- await user.click(trigger);
198
-
199
- // The selected option should have aria-selected="true"
200
- const options = screen.getAllByRole("option");
201
- const selectedOption = options.find(opt => opt.getAttribute("aria-selected") === "true");
202
- expect(selectedOption).toBeInTheDocument();
203
- expect(selectedOption).toHaveTextContent("Option 1");
204
- });
205
-
206
- test("Sets aria-expanded correctly", async () => {
207
- const { user } = setupTest();
208
- const trigger = screen.getByRole("button");
209
-
210
- expect(trigger).toHaveAttribute("aria-expanded", "false");
211
-
212
- await user.click(trigger);
213
- expect(trigger).toHaveAttribute("aria-expanded", "true");
214
-
215
- await user.click(trigger);
216
- expect(trigger).toHaveAttribute("aria-expanded", "false");
217
- });
218
- });
219
-
220
- describe("Keyboard Navigation - ArrowUp", () => {
221
- test("ArrowUp moves focus index up within bounds", async () => {
222
- const changeSpy = vi.fn();
223
- const { user } = setupTest({ onchange: changeSpy });
224
- const trigger = screen.getByRole("button");
225
-
226
- await user.click(trigger);
227
- // Navigate down twice then up once
228
- await user.keyboard("{ArrowDown}");
229
- await user.keyboard("{ArrowDown}");
230
- await user.keyboard("{ArrowUp}");
231
- await user.keyboard("{Enter}");
232
-
233
- // Started at index from click (items.findIndex matching value, or -1 => ArrowDown goes to 0, then 1, ArrowUp to 0)
234
- expect(changeSpy).toHaveBeenCalled();
235
- // After open: focusedIndex = -1 (no value), ArrowDown -> 0, ArrowDown -> 1, ArrowUp -> 0, Enter selects opt1
236
- expect(changeSpy.mock.calls[0][0]).toEqual({
237
- value: "opt1",
238
- item: { name: "Option 1", value: "opt1" },
239
- });
240
- });
241
-
242
- test("ArrowUp does not go below index 0", async () => {
243
- const changeSpy = vi.fn();
244
- const { user } = setupTest({ onchange: changeSpy });
245
- const trigger = screen.getByRole("button");
246
-
247
- await user.click(trigger);
248
- // ArrowDown to index 0, then ArrowUp should stay at 0
249
- await user.keyboard("{ArrowDown}");
250
- await user.keyboard("{ArrowUp}");
251
- await user.keyboard("{Enter}");
252
-
253
- expect(changeSpy.mock.calls[0][0]).toEqual({
254
- value: "opt1",
255
- item: { name: "Option 1", value: "opt1" },
256
- });
257
- });
258
-
259
- test("ArrowUp does nothing when dropdown is closed", async () => {
260
- const { user } = setupTest();
261
- const trigger = screen.getByRole("button");
262
-
263
- trigger.focus();
264
- await user.keyboard("{ArrowUp}");
265
- // Dropdown should still be closed
266
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
267
- });
268
- });
269
-
270
- describe("Keyboard Navigation - Tab", () => {
271
- test("Tab key closes the dropdown", async () => {
272
- const { user } = setupTest();
273
- const trigger = screen.getByRole("button");
274
-
275
- await user.click(trigger);
276
- expect(screen.getByRole("listbox")).toBeInTheDocument();
277
-
278
- await user.keyboard("{Tab}");
279
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
280
- });
281
- });
282
-
283
- describe("Keyboard Navigation - ArrowDown when closed", () => {
284
- test("ArrowDown opens dropdown and focuses first item", async () => {
285
- const changeSpy = vi.fn();
286
- const { user } = setupTest({ onchange: changeSpy });
287
- const trigger = screen.getByRole("button");
288
-
289
- trigger.focus();
290
- await user.keyboard("{ArrowDown}");
291
- // Should open the dropdown
292
- expect(screen.getByRole("listbox")).toBeInTheDocument();
293
-
294
- // Enter should select the first item (focusedIndex = 0)
295
- await user.keyboard("{Enter}");
296
- expect(changeSpy.mock.calls[0][0]).toEqual({
297
- value: "opt1",
298
- item: { name: "Option 1", value: "opt1" },
299
- });
300
- });
301
- });
302
-
303
- describe("Keyboard Navigation - ArrowDown bounds at end", () => {
304
- test("ArrowDown does not exceed items length", async () => {
305
- const changeSpy = vi.fn();
306
- const { user } = setupTest({ onchange: changeSpy });
307
- const trigger = screen.getByRole("button");
308
-
309
- await user.click(trigger);
310
- // Navigate down many times past end
311
- await user.keyboard("{ArrowDown}");
312
- await user.keyboard("{ArrowDown}");
313
- await user.keyboard("{ArrowDown}");
314
- await user.keyboard("{ArrowDown}");
315
- await user.keyboard("{ArrowDown}");
316
- await user.keyboard("{Enter}");
317
-
318
- // Should be clamped to last item (index 2)
319
- expect(changeSpy.mock.calls[0][0]).toEqual({
320
- value: "opt3",
321
- item: { name: "Option 3", value: "opt3" },
322
- });
323
- });
324
- });
325
-
326
- describe("Keyboard - disabled state", () => {
327
- test("Keyboard events are ignored when disabled", async () => {
328
- const { user } = setupTest({ disabled: true });
329
- const trigger = screen.getByRole("button");
330
-
331
- trigger.focus();
332
- await user.keyboard("{Enter}");
333
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
334
-
335
- await user.keyboard("{ArrowDown}");
336
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
337
-
338
- await user.keyboard(" ");
339
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
340
- });
341
- });
342
-
343
- describe("Select - no label rendering", () => {
344
- test("Does not render label element when label is empty", () => {
345
- const { user } = setupTest({
346
- label: "",
347
- });
348
- expect(screen.queryByRole("label")).not.toBeInTheDocument();
349
- });
350
-
351
- test("Renders label without required asterisk when not required", () => {
352
- setupTest({
353
- label: "My Label",
354
- required: false,
355
- });
356
- expect(screen.getByText("My Label")).toBeInTheDocument();
357
- expect(screen.queryByText("*")).not.toBeInTheDocument();
358
- });
359
- });
360
-
361
- describe("Select - error border styling", () => {
362
- test("Trigger has error border when error is set", () => {
363
- setupTest({ error: "Required" });
364
- const trigger = screen.getByRole("button");
365
- expect(trigger).toHaveClass("border-red-500");
366
- });
367
-
368
- test("Trigger has normal border when no error", () => {
369
- setupTest({ error: "" });
370
- const trigger = screen.getByRole("button");
371
- expect(trigger).toHaveClass("border-gray-300");
372
- });
373
- });
374
-
375
- describe("Select - disabled styling", () => {
376
- test("Disabled trigger has opacity and cursor classes", () => {
377
- setupTest({ disabled: true });
378
- const trigger = screen.getByRole("button");
379
- expect(trigger).toHaveClass("opacity-50");
380
- expect(trigger).toHaveClass("cursor-not-allowed");
381
- });
382
-
383
- test("Enabled trigger does not have disabled classes", () => {
384
- setupTest({ disabled: false });
385
- const trigger = screen.getByRole("button");
386
- expect(trigger).not.toHaveClass("opacity-50");
387
- expect(trigger).not.toHaveClass("cursor-not-allowed");
388
- });
389
- });
390
-
391
- describe("Select - size variants", () => {
392
- test("Applies small size class", () => {
393
- setupTest({ size: "sm" });
394
- const trigger = screen.getByRole("button");
395
- expect(trigger).toHaveClass("h-9");
396
- });
397
-
398
- test("Applies medium size class (default)", () => {
399
- setupTest({ size: "md" });
400
- const trigger = screen.getByRole("button");
401
- expect(trigger).toHaveClass("h-10");
402
- });
403
-
404
- test("Applies large size class", () => {
405
- setupTest({ size: "lg" });
406
- const trigger = screen.getByRole("button");
407
- expect(trigger).toHaveClass("h-12");
408
- });
409
- });
410
-
411
- describe("Select - dropdown chevron rotation", () => {
412
- test("Chevron rotates when dropdown is open", async () => {
413
- const { user } = setupTest();
414
- const trigger = screen.getByRole("button");
415
-
416
- // When closed, chevron should not have rotate-180
417
- const chevron = trigger.querySelector('svg');
418
- expect(chevron).not.toHaveClass("rotate-180");
419
-
420
- await user.click(trigger);
421
- // When open, chevron should have rotate-180
422
- expect(chevron).toHaveClass("rotate-180");
423
- });
424
- });
425
-
426
- describe("Select - option hover highlight", () => {
427
- test("Mouseenter on option updates focused index styling", async () => {
428
- const { user } = setupTest();
429
- const trigger = screen.getByRole("button");
430
-
431
- await user.click(trigger);
432
- const options = screen.getAllByRole("option");
433
-
434
- // Hover over the third option
435
- await fireEvent.mouseEnter(options[2]);
436
-
437
- // The hovered option should have the focused styling
438
- expect(options[2]).toHaveClass("bg-gray-100");
439
- });
440
- });
441
-
442
- describe("Select - trigger refocuses after selection", () => {
443
- test("Trigger element receives focus after item selection", async () => {
444
- const { user } = setupTest();
445
- const trigger = screen.getByRole("button");
446
-
447
- await user.click(trigger);
448
- await user.click(screen.getByText("Option 2"));
449
-
450
- expect(trigger).toHaveFocus();
451
- });
452
- });
453
-
454
- describe("Select - click outside when already closed", () => {
455
- test("Does not error when clicking outside while dropdown is closed", async () => {
456
- const { user } = setupTest();
457
- // Just clicking body when dropdown is closed should be fine
458
- await user.click(document.body);
459
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
460
- });
461
- });
462
-
463
- describe("Select - opening focuses current selected value", () => {
464
- test("Opens with currently selected item focused", async () => {
465
- const { user } = setupTest({ value: "opt2" });
466
- const trigger = screen.getByRole("button");
467
-
468
- await user.click(trigger);
469
- const options = screen.getAllByRole("option");
470
-
471
- // opt2 is at index 1, so the focused option should have bg-gray-100
472
- expect(options[1]).toHaveClass("bg-gray-100");
473
- });
474
- });
475
-
476
- describe("Select - Enter/Space to select focused item", () => {
477
- test("Enter key selects the currently focused item", async () => {
478
- const changeSpy = vi.fn();
479
- const { user } = setupTest({ onchange: changeSpy });
480
- const trigger = screen.getByRole("button");
481
-
482
- // Open with Enter
483
- trigger.focus();
484
- await user.keyboard("{Enter}");
485
- expect(screen.getByRole("listbox")).toBeInTheDocument();
486
-
487
- // Move down and select
488
- await user.keyboard("{ArrowDown}");
489
- await user.keyboard("{ArrowDown}");
490
- await user.keyboard("{Enter}");
491
-
492
- expect(changeSpy).toHaveBeenCalledWith({
493
- value: "opt2",
494
- item: { name: "Option 2", value: "opt2" },
495
- });
496
- });
497
-
498
- test("Space key selects the currently focused item", async () => {
499
- const changeSpy = vi.fn();
500
- const { user } = setupTest({ onchange: changeSpy });
501
- const trigger = screen.getByRole("button");
502
-
503
- // Open
504
- trigger.focus();
505
- await user.keyboard(" ");
506
- expect(screen.getByRole("listbox")).toBeInTheDocument();
507
-
508
- // Navigate to index 0, then select with Space
509
- await user.keyboard("{ArrowDown}");
510
- await user.keyboard(" ");
511
-
512
- expect(changeSpy).toHaveBeenCalled();
513
- });
514
- });
515
-
516
- describe("Select - other select instance closing", () => {
517
- test("Closes when another select instance opens", async () => {
518
- const { user } = setupTest({ id: "select-1" });
519
- const trigger1 = screen.getByRole("button");
520
-
521
- await user.click(trigger1);
522
- expect(screen.getByRole("listbox")).toBeInTheDocument();
523
-
524
- // Simulate another select opening by dispatching the custom event
525
- window.dispatchEvent(new CustomEvent('select-opened', { detail: { instanceId: 'other-instance' } }));
526
-
527
- // The Svelte reactive update may need a tick; use waitFor
528
- const { waitFor } = await import("@testing-library/svelte");
529
- await waitFor(() => {
530
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
531
- });
532
- });
533
-
534
- test("Does not close when the same instance dispatches select-opened", async () => {
535
- // We cannot easily test this from the outside since instanceId is internal,
536
- // but we can verify the dropdown stays open after its own click
537
- const { user } = setupTest();
538
- const trigger = screen.getByRole("button");
539
-
540
- await user.click(trigger);
541
- expect(screen.getByRole("listbox")).toBeInTheDocument();
542
-
543
- // Clicking the same trigger toggles it closed and open
544
- await user.click(trigger);
545
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
546
- });
547
- });
548
-
549
- describe("Select - no error text", () => {
550
- test("Does not render error paragraph when error is empty", () => {
551
- setupTest({ error: "" });
552
- // The trigger should be present with normal border
553
- const trigger = screen.getByRole("button");
554
- expect(trigger).toHaveClass("border-gray-300");
555
- // There should be no error paragraph element
556
- const errorParagraph = document.querySelector('p');
557
- expect(errorParagraph).not.toBeInTheDocument();
558
- });
559
- });
560
-
561
- describe("Select - portal mode", () => {
562
- test("Portal mode renders dropdown via portal when open", async () => {
563
- const { user } = setupTest({ portal: true });
564
- const trigger = screen.getByRole("button");
565
-
566
- await user.click(trigger);
567
-
568
- // Portal dropdown should be rendered (it uses use:portalAction to move to body)
569
- const listbox = screen.getByRole("listbox");
570
- expect(listbox).toBeInTheDocument();
571
- // Portal dropdown has 'fixed' class instead of 'absolute'
572
- expect(listbox).toHaveClass("fixed");
573
- });
574
-
575
- test("Portal mode renders items correctly", async () => {
576
- const changeSpy = vi.fn();
577
- const { user } = setupTest({ portal: true, onchange: changeSpy });
578
- const trigger = screen.getByRole("button");
579
-
580
- await user.click(trigger);
581
-
582
- const options = screen.getAllByRole("option");
583
- expect(options).toHaveLength(3);
584
-
585
- // Select an item in portal mode
586
- await user.click(screen.getByText("Option 3"));
587
- expect(changeSpy).toHaveBeenCalledWith({
588
- value: "opt3",
589
- item: { name: "Option 3", value: "opt3" },
590
- });
591
- });
592
-
593
- test("Portal dropdown is not rendered when closed", () => {
594
- setupTest({ portal: true });
595
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
596
- });
597
-
598
- test("Non-portal dropdown has absolute positioning", async () => {
599
- const { user } = setupTest({ portal: false });
600
- const trigger = screen.getByRole("button");
601
-
602
- await user.click(trigger);
603
-
604
- const listbox = screen.getByRole("listbox");
605
- expect(listbox).toHaveClass("absolute");
606
- expect(listbox).not.toHaveClass("fixed");
607
- });
608
- });
609
-
610
- describe("Select - option id attributes", () => {
611
- test("Options have correct id attributes based on select id", async () => {
612
- const { user } = setupTest({ id: "my-select" });
613
- const trigger = screen.getByRole("button");
614
-
615
- await user.click(trigger);
616
-
617
- const options = screen.getAllByRole("option");
618
- expect(options[0]).toHaveAttribute("id", "my-select-option-0");
619
- expect(options[1]).toHaveAttribute("id", "my-select-option-1");
620
- expect(options[2]).toHaveAttribute("id", "my-select-option-2");
621
- });
622
-
623
- test("Options use name for id when id is not provided", async () => {
624
- const { user } = setupTest({ name: "my-field", id: "" });
625
- const trigger = screen.getByRole("button");
626
-
627
- await user.click(trigger);
628
-
629
- const options = screen.getAllByRole("option");
630
- expect(options[0]).toHaveAttribute("id", "my-field-option-0");
631
- });
632
- });
1
+ import { render, screen, fireEvent } from "@testing-library/svelte";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { expect, describe, test, vi } from "vitest";
4
+ import Select from "./Select.svelte";
5
+
6
+ // Mock transitions to be instant for testing
7
+ vi.mock('../../utils/transitions.js', () => ({
8
+ bloom: () => ({ duration: 0, delay: 0, css: () => '' }),
9
+ safeSlide: () => ({ duration: 0, delay: 0, css: () => '' }),
10
+ }));
11
+
12
+ const sampleItems = [
13
+ { name: "Option 1", value: "opt1" },
14
+ { name: "Option 2", value: "opt2" },
15
+ { name: "Option 3", value: "opt3" },
16
+ ];
17
+
18
+ function setupTest(args = {}) {
19
+ const user = userEvent.setup();
20
+ const { component } = render(Select, {
21
+ props: {
22
+ items: sampleItems,
23
+ ...args,
24
+ },
25
+ });
26
+ return { user, component };
27
+ }
28
+
29
+ describe("Select Component Tests", () => {
30
+ test("Renders select with label", () => {
31
+ setupTest({
32
+ label: "Test Label",
33
+ id: "test-select",
34
+ });
35
+ expect(screen.getByText("Test Label")).toBeInTheDocument();
36
+ });
37
+
38
+ test("Shows placeholder when no value selected", () => {
39
+ setupTest({
40
+ placeholder: "Select an option",
41
+ });
42
+ expect(screen.getByText("Select an option")).toBeInTheDocument();
43
+ });
44
+
45
+ test("Opens dropdown on click", async () => {
46
+ const { user } = setupTest();
47
+ const trigger = screen.getByRole("button");
48
+
49
+ await user.click(trigger);
50
+
51
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
52
+ expect(screen.getByText("Option 1")).toBeInTheDocument();
53
+ expect(screen.getByText("Option 2")).toBeInTheDocument();
54
+ expect(screen.getByText("Option 3")).toBeInTheDocument();
55
+ });
56
+
57
+ test("Selects item on click", async () => {
58
+ const { user } = setupTest();
59
+ const trigger = screen.getByRole("button");
60
+
61
+ await user.click(trigger);
62
+ await user.click(screen.getByText("Option 2"));
63
+
64
+ expect(screen.getByText("Option 2")).toBeInTheDocument();
65
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
66
+ });
67
+
68
+ test("Closes dropdown after selection", async () => {
69
+ const { user } = setupTest();
70
+ const trigger = screen.getByRole("button");
71
+
72
+ await user.click(trigger);
73
+ await user.click(screen.getByText("Option 1"));
74
+
75
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
76
+ });
77
+
78
+ test("Shows selected value", () => {
79
+ setupTest({
80
+ value: "opt2",
81
+ });
82
+ expect(screen.getByText("Option 2")).toBeInTheDocument();
83
+ });
84
+
85
+ test("Dispatches change event on selection", async () => {
86
+ const changeSpy = vi.fn();
87
+ const { user } = setupTest({
88
+ onchange: changeSpy,
89
+ });
90
+
91
+ const trigger = screen.getByRole("button");
92
+ await user.click(trigger);
93
+ await user.click(screen.getByText("Option 3"));
94
+
95
+ expect(changeSpy).toHaveBeenCalled();
96
+ expect(changeSpy.mock.calls[0][0]).toEqual({
97
+ value: "opt3",
98
+ item: { name: "Option 3", value: "opt3" },
99
+ });
100
+ });
101
+
102
+ test("Shows required indicator when required", () => {
103
+ setupTest({
104
+ label: "Required Field",
105
+ required: true,
106
+ });
107
+ expect(screen.getByText("*")).toBeInTheDocument();
108
+ });
109
+
110
+ test("Displays error state", () => {
111
+ setupTest({
112
+ error: "This field is required",
113
+ });
114
+ expect(screen.getByText("This field is required")).toBeInTheDocument();
115
+ });
116
+
117
+ test("Disables select when disabled prop is true", async () => {
118
+ const { user } = setupTest({
119
+ disabled: true,
120
+ });
121
+ const trigger = screen.getByRole("button");
122
+
123
+ expect(trigger).toBeDisabled();
124
+ await user.click(trigger);
125
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
126
+ });
127
+
128
+ test("Navigates options with keyboard", async () => {
129
+ const { user } = setupTest();
130
+ const trigger = screen.getByRole("button");
131
+
132
+ await user.click(trigger);
133
+ await user.keyboard("{ArrowDown}");
134
+ await user.keyboard("{ArrowDown}");
135
+ await user.keyboard("{Enter}");
136
+
137
+ // Second option should be selected (started at 0, moved down twice)
138
+ expect(screen.getByText("Option 2")).toBeInTheDocument();
139
+ });
140
+
141
+ test("Closes dropdown on Escape key", async () => {
142
+ const { user } = setupTest();
143
+ const trigger = screen.getByRole("button");
144
+
145
+ await user.click(trigger);
146
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
147
+
148
+ await user.keyboard("{Escape}");
149
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
150
+ });
151
+
152
+ test("Opens dropdown with Enter key", async () => {
153
+ const { user } = setupTest();
154
+ const trigger = screen.getByRole("button");
155
+
156
+ trigger.focus();
157
+ await user.keyboard("{Enter}");
158
+
159
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
160
+ });
161
+
162
+ test("Opens dropdown with Space key", async () => {
163
+ const { user } = setupTest();
164
+ const trigger = screen.getByRole("button");
165
+
166
+ trigger.focus();
167
+ await user.keyboard(" ");
168
+
169
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
170
+ });
171
+
172
+ test("Applies focus ring on focus", async () => {
173
+ const { user } = setupTest();
174
+ const trigger = screen.getByRole("button");
175
+ expect(trigger).toHaveClass("focus:ring-4");
176
+ expect(trigger).toHaveClass("focus:ring-blue-300");
177
+ });
178
+
179
+ test("Closes dropdown when clicking outside", async () => {
180
+ const { user } = setupTest();
181
+ const trigger = screen.getByRole("button");
182
+
183
+ await user.click(trigger);
184
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
185
+
186
+ // Click outside by clicking on body
187
+ await user.click(document.body);
188
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
189
+ });
190
+
191
+ test("Marks selected option with aria-selected", async () => {
192
+ const { user } = setupTest({
193
+ value: "opt1",
194
+ });
195
+ const trigger = screen.getByRole("button");
196
+
197
+ await user.click(trigger);
198
+
199
+ // The selected option should have aria-selected="true"
200
+ const options = screen.getAllByRole("option");
201
+ const selectedOption = options.find(opt => opt.getAttribute("aria-selected") === "true");
202
+ expect(selectedOption).toBeInTheDocument();
203
+ expect(selectedOption).toHaveTextContent("Option 1");
204
+ });
205
+
206
+ test("Sets aria-expanded correctly", async () => {
207
+ const { user } = setupTest();
208
+ const trigger = screen.getByRole("button");
209
+
210
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
211
+
212
+ await user.click(trigger);
213
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
214
+
215
+ await user.click(trigger);
216
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
217
+ });
218
+ });
219
+
220
+ describe("Keyboard Navigation - ArrowUp", () => {
221
+ test("ArrowUp moves focus index up within bounds", async () => {
222
+ const changeSpy = vi.fn();
223
+ const { user } = setupTest({ onchange: changeSpy });
224
+ const trigger = screen.getByRole("button");
225
+
226
+ await user.click(trigger);
227
+ // Navigate down twice then up once
228
+ await user.keyboard("{ArrowDown}");
229
+ await user.keyboard("{ArrowDown}");
230
+ await user.keyboard("{ArrowUp}");
231
+ await user.keyboard("{Enter}");
232
+
233
+ // Started at index from click (items.findIndex matching value, or -1 => ArrowDown goes to 0, then 1, ArrowUp to 0)
234
+ expect(changeSpy).toHaveBeenCalled();
235
+ // After open: focusedIndex = -1 (no value), ArrowDown -> 0, ArrowDown -> 1, ArrowUp -> 0, Enter selects opt1
236
+ expect(changeSpy.mock.calls[0][0]).toEqual({
237
+ value: "opt1",
238
+ item: { name: "Option 1", value: "opt1" },
239
+ });
240
+ });
241
+
242
+ test("ArrowUp does not go below index 0", async () => {
243
+ const changeSpy = vi.fn();
244
+ const { user } = setupTest({ onchange: changeSpy });
245
+ const trigger = screen.getByRole("button");
246
+
247
+ await user.click(trigger);
248
+ // ArrowDown to index 0, then ArrowUp should stay at 0
249
+ await user.keyboard("{ArrowDown}");
250
+ await user.keyboard("{ArrowUp}");
251
+ await user.keyboard("{Enter}");
252
+
253
+ expect(changeSpy.mock.calls[0][0]).toEqual({
254
+ value: "opt1",
255
+ item: { name: "Option 1", value: "opt1" },
256
+ });
257
+ });
258
+
259
+ test("ArrowUp does nothing when dropdown is closed", async () => {
260
+ const { user } = setupTest();
261
+ const trigger = screen.getByRole("button");
262
+
263
+ trigger.focus();
264
+ await user.keyboard("{ArrowUp}");
265
+ // Dropdown should still be closed
266
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
267
+ });
268
+ });
269
+
270
+ describe("Keyboard Navigation - Tab", () => {
271
+ test("Tab key closes the dropdown", async () => {
272
+ const { user } = setupTest();
273
+ const trigger = screen.getByRole("button");
274
+
275
+ await user.click(trigger);
276
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
277
+
278
+ await user.keyboard("{Tab}");
279
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
280
+ });
281
+ });
282
+
283
+ describe("Keyboard Navigation - ArrowDown when closed", () => {
284
+ test("ArrowDown opens dropdown and focuses first item", async () => {
285
+ const changeSpy = vi.fn();
286
+ const { user } = setupTest({ onchange: changeSpy });
287
+ const trigger = screen.getByRole("button");
288
+
289
+ trigger.focus();
290
+ await user.keyboard("{ArrowDown}");
291
+ // Should open the dropdown
292
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
293
+
294
+ // Enter should select the first item (focusedIndex = 0)
295
+ await user.keyboard("{Enter}");
296
+ expect(changeSpy.mock.calls[0][0]).toEqual({
297
+ value: "opt1",
298
+ item: { name: "Option 1", value: "opt1" },
299
+ });
300
+ });
301
+ });
302
+
303
+ describe("Keyboard Navigation - ArrowDown bounds at end", () => {
304
+ test("ArrowDown does not exceed items length", async () => {
305
+ const changeSpy = vi.fn();
306
+ const { user } = setupTest({ onchange: changeSpy });
307
+ const trigger = screen.getByRole("button");
308
+
309
+ await user.click(trigger);
310
+ // Navigate down many times past end
311
+ await user.keyboard("{ArrowDown}");
312
+ await user.keyboard("{ArrowDown}");
313
+ await user.keyboard("{ArrowDown}");
314
+ await user.keyboard("{ArrowDown}");
315
+ await user.keyboard("{ArrowDown}");
316
+ await user.keyboard("{Enter}");
317
+
318
+ // Should be clamped to last item (index 2)
319
+ expect(changeSpy.mock.calls[0][0]).toEqual({
320
+ value: "opt3",
321
+ item: { name: "Option 3", value: "opt3" },
322
+ });
323
+ });
324
+ });
325
+
326
+ describe("Keyboard - disabled state", () => {
327
+ test("Keyboard events are ignored when disabled", async () => {
328
+ const { user } = setupTest({ disabled: true });
329
+ const trigger = screen.getByRole("button");
330
+
331
+ trigger.focus();
332
+ await user.keyboard("{Enter}");
333
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
334
+
335
+ await user.keyboard("{ArrowDown}");
336
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
337
+
338
+ await user.keyboard(" ");
339
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
340
+ });
341
+ });
342
+
343
+ describe("Select - no label rendering", () => {
344
+ test("Does not render label element when label is empty", () => {
345
+ const { user } = setupTest({
346
+ label: "",
347
+ });
348
+ expect(screen.queryByRole("label")).not.toBeInTheDocument();
349
+ });
350
+
351
+ test("Renders label without required asterisk when not required", () => {
352
+ setupTest({
353
+ label: "My Label",
354
+ required: false,
355
+ });
356
+ expect(screen.getByText("My Label")).toBeInTheDocument();
357
+ expect(screen.queryByText("*")).not.toBeInTheDocument();
358
+ });
359
+ });
360
+
361
+ describe("Select - error border styling", () => {
362
+ test("Trigger has error border when error is set", () => {
363
+ setupTest({ error: "Required" });
364
+ const trigger = screen.getByRole("button");
365
+ expect(trigger).toHaveClass("border-red-500");
366
+ });
367
+
368
+ test("Trigger has normal border when no error", () => {
369
+ setupTest({ error: "" });
370
+ const trigger = screen.getByRole("button");
371
+ expect(trigger).toHaveClass("border-gray-300");
372
+ });
373
+ });
374
+
375
+ describe("Select - disabled styling", () => {
376
+ test("Disabled trigger has opacity and cursor classes", () => {
377
+ setupTest({ disabled: true });
378
+ const trigger = screen.getByRole("button");
379
+ expect(trigger).toHaveClass("opacity-50");
380
+ expect(trigger).toHaveClass("cursor-not-allowed");
381
+ });
382
+
383
+ test("Enabled trigger does not have disabled classes", () => {
384
+ setupTest({ disabled: false });
385
+ const trigger = screen.getByRole("button");
386
+ expect(trigger).not.toHaveClass("opacity-50");
387
+ expect(trigger).not.toHaveClass("cursor-not-allowed");
388
+ });
389
+ });
390
+
391
+ describe("Select - size variants", () => {
392
+ test("Applies small size class", () => {
393
+ setupTest({ size: "sm" });
394
+ const trigger = screen.getByRole("button");
395
+ expect(trigger).toHaveClass("h-9");
396
+ });
397
+
398
+ test("Applies medium size class (default)", () => {
399
+ setupTest({ size: "md" });
400
+ const trigger = screen.getByRole("button");
401
+ expect(trigger).toHaveClass("h-10");
402
+ });
403
+
404
+ test("Applies large size class", () => {
405
+ setupTest({ size: "lg" });
406
+ const trigger = screen.getByRole("button");
407
+ expect(trigger).toHaveClass("h-12");
408
+ });
409
+ });
410
+
411
+ describe("Select - dropdown chevron rotation", () => {
412
+ test("Chevron rotates when dropdown is open", async () => {
413
+ const { user } = setupTest();
414
+ const trigger = screen.getByRole("button");
415
+
416
+ // When closed, chevron should not have rotate-180
417
+ const chevron = trigger.querySelector('svg');
418
+ expect(chevron).not.toHaveClass("rotate-180");
419
+
420
+ await user.click(trigger);
421
+ // When open, chevron should have rotate-180
422
+ expect(chevron).toHaveClass("rotate-180");
423
+ });
424
+ });
425
+
426
+ describe("Select - option hover highlight", () => {
427
+ test("Mouseenter on option updates focused index styling", async () => {
428
+ const { user } = setupTest();
429
+ const trigger = screen.getByRole("button");
430
+
431
+ await user.click(trigger);
432
+ const options = screen.getAllByRole("option");
433
+
434
+ // Hover over the third option
435
+ await fireEvent.mouseEnter(options[2]);
436
+
437
+ // The hovered option should have the focused styling
438
+ expect(options[2]).toHaveClass("bg-gray-100");
439
+ });
440
+ });
441
+
442
+ describe("Select - trigger refocuses after selection", () => {
443
+ test("Trigger element receives focus after item selection", async () => {
444
+ const { user } = setupTest();
445
+ const trigger = screen.getByRole("button");
446
+
447
+ await user.click(trigger);
448
+ await user.click(screen.getByText("Option 2"));
449
+
450
+ expect(trigger).toHaveFocus();
451
+ });
452
+ });
453
+
454
+ describe("Select - click outside when already closed", () => {
455
+ test("Does not error when clicking outside while dropdown is closed", async () => {
456
+ const { user } = setupTest();
457
+ // Just clicking body when dropdown is closed should be fine
458
+ await user.click(document.body);
459
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
460
+ });
461
+ });
462
+
463
+ describe("Select - opening focuses current selected value", () => {
464
+ test("Opens with currently selected item focused", async () => {
465
+ const { user } = setupTest({ value: "opt2" });
466
+ const trigger = screen.getByRole("button");
467
+
468
+ await user.click(trigger);
469
+ const options = screen.getAllByRole("option");
470
+
471
+ // opt2 is at index 1, so the focused option should have bg-gray-100
472
+ expect(options[1]).toHaveClass("bg-gray-100");
473
+ });
474
+ });
475
+
476
+ describe("Select - Enter/Space to select focused item", () => {
477
+ test("Enter key selects the currently focused item", async () => {
478
+ const changeSpy = vi.fn();
479
+ const { user } = setupTest({ onchange: changeSpy });
480
+ const trigger = screen.getByRole("button");
481
+
482
+ // Open with Enter
483
+ trigger.focus();
484
+ await user.keyboard("{Enter}");
485
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
486
+
487
+ // Move down and select
488
+ await user.keyboard("{ArrowDown}");
489
+ await user.keyboard("{ArrowDown}");
490
+ await user.keyboard("{Enter}");
491
+
492
+ expect(changeSpy).toHaveBeenCalledWith({
493
+ value: "opt2",
494
+ item: { name: "Option 2", value: "opt2" },
495
+ });
496
+ });
497
+
498
+ test("Space key selects the currently focused item", async () => {
499
+ const changeSpy = vi.fn();
500
+ const { user } = setupTest({ onchange: changeSpy });
501
+ const trigger = screen.getByRole("button");
502
+
503
+ // Open
504
+ trigger.focus();
505
+ await user.keyboard(" ");
506
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
507
+
508
+ // Navigate to index 0, then select with Space
509
+ await user.keyboard("{ArrowDown}");
510
+ await user.keyboard(" ");
511
+
512
+ expect(changeSpy).toHaveBeenCalled();
513
+ });
514
+ });
515
+
516
+ describe("Select - other select instance closing", () => {
517
+ test("Closes when another select instance opens", async () => {
518
+ const { user } = setupTest({ id: "select-1" });
519
+ const trigger1 = screen.getByRole("button");
520
+
521
+ await user.click(trigger1);
522
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
523
+
524
+ // Simulate another select opening by dispatching the custom event
525
+ window.dispatchEvent(new CustomEvent('select-opened', { detail: { instanceId: 'other-instance' } }));
526
+
527
+ // The Svelte reactive update may need a tick; use waitFor
528
+ const { waitFor } = await import("@testing-library/svelte");
529
+ await waitFor(() => {
530
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
531
+ });
532
+ });
533
+
534
+ test("Does not close when the same instance dispatches select-opened", async () => {
535
+ // We cannot easily test this from the outside since instanceId is internal,
536
+ // but we can verify the dropdown stays open after its own click
537
+ const { user } = setupTest();
538
+ const trigger = screen.getByRole("button");
539
+
540
+ await user.click(trigger);
541
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
542
+
543
+ // Clicking the same trigger toggles it closed and open
544
+ await user.click(trigger);
545
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
546
+ });
547
+ });
548
+
549
+ describe("Select - no error text", () => {
550
+ test("Does not render error paragraph when error is empty", () => {
551
+ setupTest({ error: "" });
552
+ // The trigger should be present with normal border
553
+ const trigger = screen.getByRole("button");
554
+ expect(trigger).toHaveClass("border-gray-300");
555
+ // There should be no error paragraph element
556
+ const errorParagraph = document.querySelector('p');
557
+ expect(errorParagraph).not.toBeInTheDocument();
558
+ });
559
+ });
560
+
561
+ describe("Select - portal mode", () => {
562
+ test("Portal mode renders dropdown via portal when open", async () => {
563
+ const { user } = setupTest({ portal: true });
564
+ const trigger = screen.getByRole("button");
565
+
566
+ await user.click(trigger);
567
+
568
+ // Portal dropdown should be rendered (it uses use:portalAction to move to body)
569
+ const listbox = screen.getByRole("listbox");
570
+ expect(listbox).toBeInTheDocument();
571
+ // Portal dropdown has 'fixed' class instead of 'absolute'
572
+ expect(listbox).toHaveClass("fixed");
573
+ });
574
+
575
+ test("Portal mode renders items correctly", async () => {
576
+ const changeSpy = vi.fn();
577
+ const { user } = setupTest({ portal: true, onchange: changeSpy });
578
+ const trigger = screen.getByRole("button");
579
+
580
+ await user.click(trigger);
581
+
582
+ const options = screen.getAllByRole("option");
583
+ expect(options).toHaveLength(3);
584
+
585
+ // Select an item in portal mode
586
+ await user.click(screen.getByText("Option 3"));
587
+ expect(changeSpy).toHaveBeenCalledWith({
588
+ value: "opt3",
589
+ item: { name: "Option 3", value: "opt3" },
590
+ });
591
+ });
592
+
593
+ test("Portal dropdown is not rendered when closed", () => {
594
+ setupTest({ portal: true });
595
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
596
+ });
597
+
598
+ test("Non-portal dropdown has absolute positioning", async () => {
599
+ const { user } = setupTest({ portal: false });
600
+ const trigger = screen.getByRole("button");
601
+
602
+ await user.click(trigger);
603
+
604
+ const listbox = screen.getByRole("listbox");
605
+ expect(listbox).toHaveClass("absolute");
606
+ expect(listbox).not.toHaveClass("fixed");
607
+ });
608
+ });
609
+
610
+ describe("Select - option id attributes", () => {
611
+ test("Options have correct id attributes based on select id", async () => {
612
+ const { user } = setupTest({ id: "my-select" });
613
+ const trigger = screen.getByRole("button");
614
+
615
+ await user.click(trigger);
616
+
617
+ const options = screen.getAllByRole("option");
618
+ expect(options[0]).toHaveAttribute("id", "my-select-option-0");
619
+ expect(options[1]).toHaveAttribute("id", "my-select-option-1");
620
+ expect(options[2]).toHaveAttribute("id", "my-select-option-2");
621
+ });
622
+
623
+ test("Options use name for id when id is not provided", async () => {
624
+ const { user } = setupTest({ name: "my-field", id: "" });
625
+ const trigger = screen.getByRole("button");
626
+
627
+ await user.click(trigger);
628
+
629
+ const options = screen.getAllByRole("option");
630
+ expect(options[0]).toHaveAttribute("id", "my-field-option-0");
631
+ });
632
+ });