@getmicdrop/svelte-components 5.5.1 → 5.5.5

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 (472) hide show
  1. package/dist/calendar/AboutShow/AboutShow.spec.d.ts +2 -0
  2. package/dist/calendar/AboutShow/AboutShow.spec.d.ts.map +1 -0
  3. package/dist/calendar/AboutShow/AboutShow.spec.js +791 -0
  4. package/dist/calendar/AboutShow/AboutShow.svelte +172 -172
  5. package/dist/calendar/Calendar/MiniMonthCalendar.spec.d.ts +2 -0
  6. package/dist/calendar/Calendar/MiniMonthCalendar.spec.d.ts.map +1 -0
  7. package/dist/calendar/Calendar/MiniMonthCalendar.spec.js +1191 -0
  8. package/dist/calendar/Calendar/MiniMonthCalendar.svelte +782 -782
  9. package/dist/calendar/FAQs/FAQs.spec.d.ts +2 -0
  10. package/dist/calendar/FAQs/FAQs.spec.d.ts.map +1 -0
  11. package/dist/calendar/FAQs/FAQs.spec.js +238 -0
  12. package/dist/calendar/FAQs/FAQs.svelte +75 -75
  13. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts +2 -0
  14. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts.map +1 -0
  15. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.js +420 -0
  16. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +126 -126
  17. package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts +2 -0
  18. package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts.map +1 -0
  19. package/dist/calendar/OrderSummary/OrderSummary.spec.js +808 -0
  20. package/dist/calendar/OrderSummary/OrderSummary.svelte +367 -367
  21. package/dist/calendar/PublicCard/PublicCard.spec.d.ts +2 -0
  22. package/dist/calendar/PublicCard/PublicCard.spec.d.ts.map +1 -0
  23. package/dist/calendar/PublicCard/PublicCard.spec.js +301 -0
  24. package/dist/calendar/PublicCard/PublicCard.svelte +134 -134
  25. package/dist/calendar/ShowCard/ShowCard.spec.d.ts +2 -0
  26. package/dist/calendar/ShowCard/ShowCard.spec.d.ts.map +1 -0
  27. package/dist/calendar/ShowCard/ShowCard.spec.js +714 -0
  28. package/dist/calendar/ShowCard/ShowCard.svelte +157 -157
  29. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts +2 -0
  30. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts.map +1 -0
  31. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.js +241 -0
  32. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +61 -61
  33. package/dist/components/Layout/Grid.svelte +4 -4
  34. package/dist/components/Layout/Section.spec.d.ts +2 -0
  35. package/dist/components/Layout/Section.spec.d.ts.map +1 -0
  36. package/dist/components/Layout/Section.spec.js +149 -0
  37. package/dist/components/Layout/Section.svelte +80 -80
  38. package/dist/components/Layout/Sidebar.spec.d.ts +2 -0
  39. package/dist/components/Layout/Sidebar.spec.d.ts.map +1 -0
  40. package/dist/components/Layout/Sidebar.spec.js +186 -0
  41. package/dist/components/Layout/Sidebar.svelte +108 -108
  42. package/dist/components/Layout/Stack.spec.js +3 -3
  43. package/dist/components/Layout/Stack.svelte +6 -6
  44. package/dist/constants/formOptions.spec.js +9 -5
  45. package/dist/constants/validation.js +91 -91
  46. package/dist/constants/validation.spec.js +64 -64
  47. package/dist/datetime/__tests__/format.test.js +1 -1
  48. package/dist/datetime/__tests__/parse.test.js +1 -1
  49. package/dist/datetime/__tests__/timezone.test.js +124 -2
  50. package/dist/datetime/parse.js +1 -1
  51. package/dist/forms/createFieldTracker.spec.d.ts +2 -0
  52. package/dist/forms/createFieldTracker.spec.d.ts.map +1 -0
  53. package/dist/forms/createFieldTracker.spec.js +343 -0
  54. package/dist/forms/createFormStore.spec.d.ts +2 -0
  55. package/dist/forms/createFormStore.spec.d.ts.map +1 -0
  56. package/dist/forms/createFormStore.spec.js +689 -0
  57. package/dist/forms/createFormStore.svelte.js +0 -1
  58. package/dist/index.d.ts +5 -112
  59. package/dist/index.js +40 -225
  60. package/dist/patterns/data/DataGrid.spec.d.ts +2 -0
  61. package/dist/patterns/data/DataGrid.spec.d.ts.map +1 -0
  62. package/dist/patterns/data/DataGrid.spec.js +159 -0
  63. package/dist/patterns/data/DataGrid.svelte +45 -45
  64. package/dist/patterns/data/DataList.spec.d.ts +2 -0
  65. package/dist/patterns/data/DataList.spec.d.ts.map +1 -0
  66. package/dist/patterns/data/DataList.spec.js +158 -0
  67. package/dist/patterns/data/DataList.svelte +24 -24
  68. package/dist/patterns/data/DataTable.spec.d.ts +2 -0
  69. package/dist/patterns/data/DataTable.spec.d.ts.map +1 -0
  70. package/dist/patterns/data/DataTable.spec.js +196 -0
  71. package/dist/patterns/data/DataTable.svelte +36 -36
  72. package/dist/patterns/forms/FormActions.spec.js +95 -88
  73. package/dist/patterns/forms/FormActions.stories.svelte +97 -97
  74. package/dist/patterns/forms/FormActions.svelte +46 -46
  75. package/dist/patterns/forms/FormGrid.spec.d.ts +2 -0
  76. package/dist/patterns/forms/FormGrid.spec.d.ts.map +1 -0
  77. package/dist/patterns/forms/FormGrid.spec.js +125 -0
  78. package/dist/patterns/forms/FormGrid.svelte +33 -33
  79. package/dist/patterns/forms/FormSection.spec.d.ts +2 -0
  80. package/dist/patterns/forms/FormSection.spec.d.ts.map +1 -0
  81. package/dist/patterns/forms/FormSection.spec.js +153 -0
  82. package/dist/patterns/forms/FormSection.svelte +32 -32
  83. package/dist/patterns/forms/FormValidationSummary.stories.svelte +83 -83
  84. package/dist/patterns/forms/FormValidationSummary.svelte +33 -33
  85. package/dist/patterns/layout/Sidebar.spec.d.ts +2 -0
  86. package/dist/patterns/layout/Sidebar.spec.d.ts.map +1 -0
  87. package/dist/patterns/layout/Sidebar.spec.js +159 -0
  88. package/dist/patterns/layout/Sidebar.svelte +39 -39
  89. package/dist/patterns/navigation/BottomNav.stories.svelte +117 -117
  90. package/dist/patterns/navigation/BottomNav.svelte +20 -20
  91. package/dist/patterns/navigation/Header.spec.js +33 -24
  92. package/dist/patterns/navigation/Header.stories.svelte +77 -77
  93. package/dist/patterns/navigation/Header.svelte +193 -193
  94. package/dist/patterns/page/PageHeader.spec.d.ts +2 -0
  95. package/dist/patterns/page/PageHeader.spec.d.ts.map +1 -0
  96. package/dist/patterns/page/PageHeader.spec.js +167 -0
  97. package/dist/patterns/page/PageHeader.svelte +18 -18
  98. package/dist/patterns/page/PageLayout.spec.d.ts +2 -0
  99. package/dist/patterns/page/PageLayout.spec.d.ts.map +1 -0
  100. package/dist/patterns/page/PageLayout.spec.js +145 -0
  101. package/dist/patterns/page/PageLayout.svelte +40 -40
  102. package/dist/patterns/page/PageLoader.spec.js +57 -54
  103. package/dist/patterns/page/PageLoader.stories.svelte +137 -137
  104. package/dist/patterns/page/PageLoader.svelte +24 -24
  105. package/dist/patterns/page/SectionHeader.spec.d.ts +2 -0
  106. package/dist/patterns/page/SectionHeader.spec.d.ts.map +1 -0
  107. package/dist/patterns/page/SectionHeader.spec.js +197 -0
  108. package/dist/patterns/page/SectionHeader.svelte +29 -29
  109. package/dist/presets/badges.js +112 -112
  110. package/dist/presets/badges.spec.d.ts +2 -0
  111. package/dist/presets/badges.spec.d.ts.map +1 -0
  112. package/dist/presets/badges.spec.js +172 -0
  113. package/dist/presets/buttons.js +76 -76
  114. package/dist/presets/buttons.spec.d.ts +2 -0
  115. package/dist/presets/buttons.spec.d.ts.map +1 -0
  116. package/dist/presets/buttons.spec.js +135 -0
  117. package/dist/presets/index.js +9 -9
  118. package/dist/primitives/Accordion/Accordion.spec.d.ts +2 -0
  119. package/dist/primitives/Accordion/Accordion.spec.d.ts.map +1 -0
  120. package/dist/primitives/Accordion/Accordion.spec.js +83 -0
  121. package/dist/primitives/Accordion/Accordion.stories.svelte +75 -75
  122. package/dist/primitives/Accordion/Accordion.svelte +42 -42
  123. package/dist/primitives/Accordion/AccordionItem.spec.d.ts +2 -0
  124. package/dist/primitives/Accordion/AccordionItem.spec.d.ts.map +1 -0
  125. package/dist/primitives/Accordion/AccordionItem.spec.js +661 -0
  126. package/dist/primitives/Accordion/AccordionItem.svelte +95 -95
  127. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte +107 -0
  128. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts +35 -0
  129. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts.map +1 -0
  130. package/dist/primitives/Alert/Alert.spec.js +173 -170
  131. package/dist/primitives/Alert/Alert.stories.svelte +88 -88
  132. package/dist/primitives/Alert/Alert.svelte +27 -27
  133. package/dist/primitives/Avatar/Avatar.spec.d.ts +2 -0
  134. package/dist/primitives/Avatar/Avatar.spec.d.ts.map +1 -0
  135. package/dist/primitives/Avatar/Avatar.spec.js +211 -0
  136. package/dist/primitives/Avatar/Avatar.stories.svelte +94 -94
  137. package/dist/primitives/Avatar/Avatar.svelte +66 -66
  138. package/dist/primitives/Badges/Badge.spec.js +144 -103
  139. package/dist/primitives/Badges/Badge.stories.svelte +86 -86
  140. package/dist/primitives/Badges/Badge.svelte +79 -79
  141. package/dist/primitives/BottomSheet/BottomSheet.spec.js +136 -127
  142. package/dist/primitives/BottomSheet/BottomSheet.stories.svelte +83 -83
  143. package/dist/primitives/BottomSheet/BottomSheet.svelte +100 -100
  144. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +13 -0
  145. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +7 -0
  146. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts.map +1 -0
  147. package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +122 -120
  148. package/dist/primitives/Breadcrumb/Breadcrumb.stories.svelte +23 -23
  149. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +89 -89
  150. package/dist/primitives/Button/Button.spec.js +223 -211
  151. package/dist/primitives/Button/Button.stories.svelte +76 -76
  152. package/dist/primitives/Button/Button.svelte +270 -270
  153. package/dist/primitives/Button/ButtonSaveDemo.spec.js +146 -48
  154. package/dist/primitives/Button/ButtonSaveDemo.svelte +25 -25
  155. package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts +2 -0
  156. package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts.map +1 -0
  157. package/dist/primitives/Button/ButtonVariantShowcase.spec.js +202 -0
  158. package/dist/primitives/Button/ButtonVariantShowcase.svelte +129 -129
  159. package/dist/primitives/Card.spec.js +49 -49
  160. package/dist/primitives/Card.stories.svelte +22 -22
  161. package/dist/primitives/Card.svelte +28 -28
  162. package/dist/primitives/Checkbox/Checkbox.spec.d.ts +2 -0
  163. package/dist/primitives/Checkbox/Checkbox.spec.d.ts.map +1 -0
  164. package/dist/primitives/Checkbox/Checkbox.spec.js +252 -0
  165. package/dist/primitives/Checkbox/Checkbox.stories.svelte +84 -84
  166. package/dist/primitives/Checkbox/Checkbox.svelte +88 -88
  167. package/dist/primitives/DarkModeToggle.spec.js +390 -357
  168. package/dist/primitives/DarkModeToggle.stories.svelte +57 -57
  169. package/dist/primitives/DarkModeToggle.svelte +136 -136
  170. package/dist/primitives/Drawer/Drawer.spec.d.ts +2 -0
  171. package/dist/primitives/Drawer/Drawer.spec.d.ts.map +1 -0
  172. package/dist/primitives/Drawer/Drawer.spec.js +212 -0
  173. package/dist/primitives/Drawer/Drawer.stories.svelte +80 -80
  174. package/dist/primitives/Drawer/Drawer.svelte +120 -120
  175. package/dist/primitives/Dropdown/Dropdown.spec.d.ts +2 -0
  176. package/dist/primitives/Dropdown/Dropdown.spec.d.ts.map +1 -0
  177. package/dist/primitives/Dropdown/Dropdown.spec.js +366 -0
  178. package/dist/primitives/Dropdown/Dropdown.stories.svelte +137 -137
  179. package/dist/primitives/Dropdown/Dropdown.svelte +14 -14
  180. package/dist/primitives/Dropdown/DropdownItem.spec.d.ts +2 -0
  181. package/dist/primitives/Dropdown/DropdownItem.spec.d.ts.map +1 -0
  182. package/dist/primitives/Dropdown/DropdownItem.spec.js +182 -0
  183. package/dist/primitives/Dropdown/DropdownItem.svelte +80 -80
  184. package/dist/primitives/Icons/ArrowLeft.svelte +8 -8
  185. package/dist/primitives/Icons/ArrowRight.svelte +8 -8
  186. package/dist/primitives/Icons/Availability.svelte +14 -14
  187. package/dist/primitives/Icons/Back.svelte +14 -14
  188. package/dist/primitives/Icons/CheckCircle.svelte +6 -6
  189. package/dist/primitives/Icons/CheckCircleOutline.svelte +15 -15
  190. package/dist/primitives/Icons/ChevronLeft.svelte +4 -4
  191. package/dist/primitives/Icons/ChevronRight.svelte +4 -4
  192. package/dist/primitives/Icons/Copy.svelte +15 -15
  193. package/dist/primitives/Icons/Cross.svelte +5 -5
  194. package/dist/primitives/Icons/DownArrow.svelte +8 -8
  195. package/dist/primitives/Icons/ErrorCircle.svelte +6 -6
  196. package/dist/primitives/Icons/FacebookIcon.svelte +2 -2
  197. package/dist/primitives/Icons/Home.svelte +15 -15
  198. package/dist/primitives/Icons/Icon.spec.js +169 -169
  199. package/dist/primitives/Icons/Icon.stories.svelte +100 -100
  200. package/dist/primitives/Icons/Icon.svelte +52 -52
  201. package/dist/primitives/Icons/IconGallery.stories.svelte +235 -235
  202. package/dist/primitives/Icons/Info.svelte +7 -7
  203. package/dist/primitives/Icons/InstagramIcon.svelte +4 -4
  204. package/dist/primitives/Icons/LogoInstagram.svelte +2 -2
  205. package/dist/primitives/Icons/Message.svelte +15 -15
  206. package/dist/primitives/Icons/MoonIcon.svelte +5 -5
  207. package/dist/primitives/Icons/More.svelte +21 -21
  208. package/dist/primitives/Icons/MoreHori.spec.js +61 -61
  209. package/dist/primitives/Icons/MoreHori.svelte +22 -22
  210. package/dist/primitives/Icons/Notification.svelte +14 -14
  211. package/dist/primitives/Icons/Payment.svelte +14 -14
  212. package/dist/primitives/Icons/Profile.svelte +21 -21
  213. package/dist/primitives/Icons/Reload.svelte +29 -29
  214. package/dist/primitives/Icons/Shows.svelte +21 -21
  215. package/dist/primitives/Icons/Signout.svelte +21 -21
  216. package/dist/primitives/Icons/SunIcon.svelte +8 -8
  217. package/dist/primitives/Icons/TiktokIcon.svelte +2 -2
  218. package/dist/primitives/Icons/TwitterIcon.svelte +2 -2
  219. package/dist/primitives/Icons/WarningIcon.spec.js +18 -18
  220. package/dist/primitives/Icons/WarningIcon.svelte +5 -5
  221. package/dist/primitives/Icons/iconTestUtils.spec.d.ts +2 -0
  222. package/dist/primitives/Icons/iconTestUtils.spec.d.ts.map +1 -0
  223. package/dist/primitives/Icons/iconTestUtils.spec.js +235 -0
  224. package/dist/primitives/Input/Input.spec.js +573 -573
  225. package/dist/primitives/Input/Input.stories.svelte +139 -139
  226. package/dist/primitives/Input/Input.svelte +384 -397
  227. package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
  228. package/dist/primitives/Input/Select.spec.js +212 -218
  229. package/dist/primitives/Input/Select.stories.svelte +112 -112
  230. package/dist/primitives/Input/Select.svelte +128 -128
  231. package/dist/primitives/Input/Textarea.spec.d.ts +2 -0
  232. package/dist/primitives/Input/Textarea.spec.d.ts.map +1 -0
  233. package/dist/primitives/Input/Textarea.spec.js +255 -0
  234. package/dist/primitives/Input/Textarea.stories.svelte +137 -137
  235. package/dist/primitives/Input/Textarea.svelte +35 -35
  236. package/dist/primitives/Label/Label.spec.d.ts +2 -0
  237. package/dist/primitives/Label/Label.spec.d.ts.map +1 -0
  238. package/dist/primitives/Label/Label.spec.js +157 -0
  239. package/dist/primitives/Label/Label.svelte +37 -37
  240. package/dist/primitives/Modal/Modal.spec.js +99 -95
  241. package/dist/primitives/Modal/Modal.stories.svelte +86 -86
  242. package/dist/primitives/Modal/Modal.svelte +158 -158
  243. package/dist/primitives/Modal/ModalTestWrapper.svelte +65 -0
  244. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +23 -0
  245. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts.map +1 -0
  246. package/dist/primitives/NumberInput/NumberInput.spec.d.ts +2 -0
  247. package/dist/primitives/NumberInput/NumberInput.spec.d.ts.map +1 -0
  248. package/dist/primitives/NumberInput/NumberInput.spec.js +235 -0
  249. package/dist/primitives/NumberInput/NumberInput.svelte +106 -106
  250. package/dist/primitives/Pagination/Pagination.spec.d.ts +2 -0
  251. package/dist/primitives/Pagination/Pagination.spec.d.ts.map +1 -0
  252. package/dist/primitives/Pagination/Pagination.spec.js +266 -0
  253. package/dist/primitives/Pagination/Pagination.stories.svelte +76 -76
  254. package/dist/primitives/Pagination/Pagination.svelte +261 -261
  255. package/dist/primitives/Radio/Radio.spec.d.ts +2 -0
  256. package/dist/primitives/Radio/Radio.spec.d.ts.map +1 -0
  257. package/dist/primitives/Radio/Radio.spec.js +206 -0
  258. package/dist/primitives/Radio/Radio.stories.svelte +80 -80
  259. package/dist/primitives/Radio/Radio.svelte +67 -67
  260. package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts +2 -0
  261. package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts.map +1 -0
  262. package/dist/primitives/Skeleton/CardPlaceholder.spec.js +156 -0
  263. package/dist/primitives/Skeleton/CardPlaceholder.svelte +87 -87
  264. package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts +2 -0
  265. package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts.map +1 -0
  266. package/dist/primitives/Skeleton/ImagePlaceholder.spec.js +120 -0
  267. package/dist/primitives/Skeleton/ImagePlaceholder.svelte +59 -59
  268. package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts +2 -0
  269. package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts.map +1 -0
  270. package/dist/primitives/Skeleton/ListPlaceholder.spec.js +220 -0
  271. package/dist/primitives/Skeleton/ListPlaceholder.svelte +76 -76
  272. package/dist/primitives/Skeleton/Skeleton.spec.d.ts +2 -0
  273. package/dist/primitives/Skeleton/Skeleton.spec.d.ts.map +1 -0
  274. package/dist/primitives/Skeleton/Skeleton.spec.js +173 -0
  275. package/dist/primitives/Skeleton/Skeleton.stories.svelte +151 -151
  276. package/dist/primitives/Skeleton/Skeleton.svelte +26 -26
  277. package/dist/primitives/Spinner/Spinner.spec.js +71 -75
  278. package/dist/primitives/Spinner/Spinner.stories.svelte +29 -29
  279. package/dist/primitives/Spinner/Spinner.svelte +20 -20
  280. package/dist/primitives/Tabs/TabItem.spec.d.ts +2 -0
  281. package/dist/primitives/Tabs/TabItem.spec.d.ts.map +1 -0
  282. package/dist/primitives/Tabs/TabItem.spec.js +130 -0
  283. package/dist/primitives/Tabs/TabItem.svelte +49 -49
  284. package/dist/primitives/Tabs/Tabs.spec.d.ts +2 -0
  285. package/dist/primitives/Tabs/Tabs.spec.d.ts.map +1 -0
  286. package/dist/primitives/Tabs/Tabs.spec.js +295 -0
  287. package/dist/primitives/Tabs/Tabs.stories.svelte +112 -112
  288. package/dist/primitives/Tabs/Tabs.svelte +123 -123
  289. package/dist/primitives/Tabs/TabsWithItems.test.svelte +18 -0
  290. package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts +16 -0
  291. package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts.map +1 -0
  292. package/dist/primitives/Toggle.spec.js +143 -127
  293. package/dist/primitives/Toggle.stories.svelte +92 -92
  294. package/dist/primitives/Toggle.svelte +71 -71
  295. package/dist/primitives/Typography/Typography.spec.d.ts +2 -0
  296. package/dist/primitives/Typography/Typography.spec.d.ts.map +1 -0
  297. package/dist/primitives/Typography/Typography.spec.js +183 -0
  298. package/dist/primitives/Typography/Typography.svelte +53 -53
  299. package/dist/primitives/ValidationError.spec.js +103 -103
  300. package/dist/primitives/ValidationError.stories.svelte +69 -69
  301. package/dist/primitives/ValidationError.svelte +29 -29
  302. package/dist/primitives/index.d.ts +1 -0
  303. package/dist/primitives/index.js +3 -0
  304. package/dist/recipes/CropImage/CropImage.spec.js +208 -216
  305. package/dist/recipes/CropImage/CropImage.stories.svelte +104 -104
  306. package/dist/recipes/CropImage/CropImage.svelte +238 -238
  307. package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts +2 -0
  308. package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts.map +1 -0
  309. package/dist/recipes/ImageUploader/ImageUploader.spec.js +1351 -0
  310. package/dist/recipes/ImageUploader/ImageUploader.stories.svelte +125 -125
  311. package/dist/recipes/ImageUploader/ImageUploader.svelte +804 -804
  312. package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts +2 -0
  313. package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts.map +1 -0
  314. package/dist/recipes/SuperLogin/SuperLogin.spec.js +1436 -0
  315. package/dist/recipes/SuperLogin/SuperLogin.svelte +7 -6
  316. package/dist/recipes/SuperLogin/SuperLogin.svelte.d.ts.map +1 -1
  317. package/dist/recipes/Toaster/Toaster.stories.svelte +62 -62
  318. package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts +2 -0
  319. package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts.map +1 -0
  320. package/dist/recipes/feedback/EmptyState/EmptyState.spec.js +202 -0
  321. package/dist/recipes/feedback/EmptyState/EmptyState.svelte +1 -1
  322. package/dist/recipes/feedback/ErrorDisplay.spec.js +69 -69
  323. package/dist/recipes/feedback/ErrorDisplay.stories.svelte +101 -101
  324. package/dist/recipes/feedback/ErrorDisplay.svelte +1 -1
  325. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.spec.js +133 -129
  326. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.svelte +157 -157
  327. package/dist/recipes/fields/CheckboxField.spec.d.ts +2 -0
  328. package/dist/recipes/fields/CheckboxField.spec.d.ts.map +1 -0
  329. package/dist/recipes/fields/CheckboxField.spec.js +135 -0
  330. package/dist/recipes/fields/CheckboxField.svelte +85 -85
  331. package/dist/recipes/fields/FormField.spec.d.ts +2 -0
  332. package/dist/recipes/fields/FormField.spec.d.ts.map +1 -0
  333. package/dist/recipes/fields/FormField.spec.js +159 -0
  334. package/dist/recipes/fields/FormField.svelte +58 -58
  335. package/dist/recipes/fields/RadioGroup.spec.d.ts +2 -0
  336. package/dist/recipes/fields/RadioGroup.spec.d.ts.map +1 -0
  337. package/dist/recipes/fields/RadioGroup.spec.js +199 -0
  338. package/dist/recipes/fields/RadioGroup.svelte +95 -95
  339. package/dist/recipes/fields/SelectField.spec.d.ts +2 -0
  340. package/dist/recipes/fields/SelectField.spec.d.ts.map +1 -0
  341. package/dist/recipes/fields/SelectField.spec.js +188 -0
  342. package/dist/recipes/fields/SelectField.svelte +80 -80
  343. package/dist/recipes/fields/TextareaField.spec.d.ts +2 -0
  344. package/dist/recipes/fields/TextareaField.spec.d.ts.map +1 -0
  345. package/dist/recipes/fields/TextareaField.spec.js +205 -0
  346. package/dist/recipes/fields/TextareaField.svelte +97 -97
  347. package/dist/recipes/fields/ToggleField.spec.d.ts +2 -0
  348. package/dist/recipes/fields/ToggleField.spec.d.ts.map +1 -0
  349. package/dist/recipes/fields/ToggleField.spec.js +153 -0
  350. package/dist/recipes/fields/ToggleField.svelte +60 -60
  351. package/dist/recipes/fields/index.js +7 -7
  352. package/dist/recipes/inputs/MultiSelect.spec.js +258 -257
  353. package/dist/recipes/inputs/MultiSelect.stories.svelte +133 -133
  354. package/dist/recipes/inputs/MultiSelect.svelte +256 -249
  355. package/dist/recipes/inputs/MultiSelect.svelte.d.ts +2 -0
  356. package/dist/recipes/inputs/MultiSelect.svelte.d.ts.map +1 -1
  357. package/dist/recipes/inputs/OTPInput.spec.js +251 -238
  358. package/dist/recipes/inputs/OTPInput.stories.svelte +162 -162
  359. package/dist/recipes/inputs/OTPInput.svelte +29 -29
  360. package/dist/recipes/inputs/PasswordInput.spec.d.ts +2 -0
  361. package/dist/recipes/inputs/PasswordInput.spec.d.ts.map +1 -0
  362. package/dist/recipes/inputs/PasswordInput.spec.js +410 -0
  363. package/dist/recipes/inputs/PasswordInput.svelte +22 -22
  364. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +245 -165
  365. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte +43 -43
  366. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte +71 -0
  367. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts +9 -0
  368. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts.map +1 -0
  369. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.spec.js +1139 -193
  370. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.stories.svelte +123 -123
  371. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte +326 -326
  372. package/dist/recipes/inputs/Search.spec.d.ts +2 -0
  373. package/dist/recipes/inputs/Search.spec.d.ts.map +1 -0
  374. package/dist/recipes/inputs/Search.spec.js +177 -0
  375. package/dist/recipes/inputs/Search.svelte +37 -37
  376. package/dist/recipes/inputs/SelectDropdown.spec.d.ts +2 -0
  377. package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +1 -0
  378. package/dist/recipes/inputs/SelectDropdown.spec.js +512 -0
  379. package/dist/recipes/inputs/SelectDropdown.svelte +57 -57
  380. package/dist/recipes/modals/AlertModal.spec.d.ts +2 -0
  381. package/dist/recipes/modals/AlertModal.spec.d.ts.map +1 -0
  382. package/dist/recipes/modals/AlertModal.spec.js +432 -0
  383. package/dist/recipes/modals/AlertModal.svelte +130 -130
  384. package/dist/recipes/modals/ConfirmationModal.spec.js +206 -191
  385. package/dist/recipes/modals/ConfirmationModal.stories.svelte +119 -119
  386. package/dist/recipes/modals/ConfirmationModal.svelte +152 -152
  387. package/dist/recipes/modals/InputModal.spec.d.ts +2 -0
  388. package/dist/recipes/modals/InputModal.spec.d.ts.map +1 -0
  389. package/dist/recipes/modals/InputModal.spec.js +872 -0
  390. package/dist/recipes/modals/InputModal.svelte +182 -182
  391. package/dist/recipes/modals/ModalStateManager.spec.js +100 -100
  392. package/dist/recipes/modals/ModalStateManager.svelte +77 -77
  393. package/dist/recipes/modals/ModalTestWrapper.spec.d.ts +2 -0
  394. package/dist/recipes/modals/ModalTestWrapper.spec.d.ts.map +1 -0
  395. package/dist/recipes/modals/ModalTestWrapper.spec.js +502 -0
  396. package/dist/recipes/modals/ModalTestWrapper.svelte +65 -65
  397. package/dist/recipes/modals/StatusModal.spec.d.ts +2 -0
  398. package/dist/recipes/modals/StatusModal.spec.d.ts.map +1 -0
  399. package/dist/recipes/modals/StatusModal.spec.js +599 -0
  400. package/dist/recipes/modals/StatusModal.svelte +206 -206
  401. package/dist/services/EventService.js +75 -75
  402. package/dist/services/EventService.spec.js +217 -217
  403. package/dist/services/ShowService.spec.js +345 -342
  404. package/dist/stores/auth.js +36 -36
  405. package/dist/stores/auth.spec.js +139 -139
  406. package/dist/stores/toaster.js +13 -13
  407. package/dist/stories/ButtonAuditDashboard.spec.d.ts +2 -0
  408. package/dist/stories/ButtonAuditDashboard.spec.d.ts.map +1 -0
  409. package/dist/stories/ButtonAuditDashboard.spec.js +913 -0
  410. package/dist/stories/ButtonAuditReview.spec.d.ts +2 -0
  411. package/dist/stories/ButtonAuditReview.spec.d.ts.map +1 -0
  412. package/dist/stories/ButtonAuditReview.spec.js +422 -0
  413. package/dist/stories/ButtonAuditReview.stories.svelte +14 -14
  414. package/dist/stories/ButtonAuditReview.svelte +427 -427
  415. package/dist/stories/ButtonGridView.spec.d.ts +2 -0
  416. package/dist/stories/ButtonGridView.spec.d.ts.map +1 -0
  417. package/dist/stories/ButtonGridView.spec.js +667 -0
  418. package/dist/stories/ButtonShowcase.spec.d.ts +2 -0
  419. package/dist/stories/ButtonShowcase.spec.d.ts.map +1 -0
  420. package/dist/stories/ButtonShowcase.spec.js +499 -0
  421. package/dist/stories/PatternsGallery.spec.d.ts +2 -0
  422. package/dist/stories/PatternsGallery.spec.d.ts.map +1 -0
  423. package/dist/stories/PatternsGallery.spec.js +514 -0
  424. package/dist/stories/PatternsGallery.stories.svelte +19 -19
  425. package/dist/stories/PatternsGallery.svelte +206 -206
  426. package/dist/stories/PrimitivesGallery.spec.d.ts +2 -0
  427. package/dist/stories/PrimitivesGallery.spec.d.ts.map +1 -0
  428. package/dist/stories/PrimitivesGallery.spec.js +813 -0
  429. package/dist/stories/PrimitivesGallery.stories.svelte +19 -19
  430. package/dist/stories/PrimitivesGallery.svelte +725 -725
  431. package/dist/stories/RecipesGallery.spec.d.ts +2 -0
  432. package/dist/stories/RecipesGallery.spec.d.ts.map +1 -0
  433. package/dist/stories/RecipesGallery.spec.js +299 -0
  434. package/dist/stories/RecipesGallery.stories.svelte +19 -19
  435. package/dist/stories/RecipesGallery.svelte +271 -271
  436. package/dist/stories/button-audit-manifest.json +11186 -11186
  437. package/dist/stripe/useStripeTheme.spec.d.ts +2 -0
  438. package/dist/stripe/useStripeTheme.spec.d.ts.map +1 -0
  439. package/dist/stripe/useStripeTheme.spec.js +793 -0
  440. package/dist/tailwind/preset.cjs +82 -82
  441. package/dist/telemetry.d.ts.map +1 -1
  442. package/dist/telemetry.js +6 -5
  443. package/dist/telemetry.spec.js +495 -12
  444. package/dist/tokens/__tests__/colors.test.d.ts +2 -0
  445. package/dist/tokens/__tests__/colors.test.d.ts.map +1 -0
  446. package/dist/tokens/__tests__/colors.test.js +152 -0
  447. package/dist/tokens/__tests__/radius.test.d.ts +2 -0
  448. package/dist/tokens/__tests__/radius.test.d.ts.map +1 -0
  449. package/dist/tokens/__tests__/radius.test.js +118 -0
  450. package/dist/tokens/__tests__/shadows.test.d.ts +2 -0
  451. package/dist/tokens/__tests__/shadows.test.d.ts.map +1 -0
  452. package/dist/tokens/__tests__/shadows.test.js +105 -0
  453. package/dist/tokens/__tests__/spacing.test.js +11 -8
  454. package/dist/tokens/__tests__/typography.test.d.ts +2 -0
  455. package/dist/tokens/__tests__/typography.test.d.ts.map +1 -0
  456. package/dist/tokens/__tests__/typography.test.js +156 -0
  457. package/dist/tokens/__tests__/z-index.test.d.ts +2 -0
  458. package/dist/tokens/__tests__/z-index.test.d.ts.map +1 -0
  459. package/dist/tokens/__tests__/z-index.test.js +121 -0
  460. package/dist/tokens/tokens.css +87 -87
  461. package/dist/utils/apiConfig.spec.js +102 -1
  462. package/dist/utils/formatters.spec.d.ts +2 -0
  463. package/dist/utils/formatters.spec.d.ts.map +1 -0
  464. package/dist/utils/formatters.spec.js +82 -0
  465. package/dist/utils/transitions.d.ts +24 -0
  466. package/dist/utils/transitions.d.ts.map +1 -0
  467. package/dist/utils/transitions.js +62 -0
  468. package/dist/utils/transitions.spec.d.ts +2 -0
  469. package/dist/utils/transitions.spec.d.ts.map +1 -0
  470. package/dist/utils/transitions.spec.js +130 -0
  471. package/dist/utils/utils.js +354 -354
  472. package/package.json +288 -283
@@ -1,300 +1,1246 @@
1
- import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from 'vitest';
2
- import { render, fireEvent, waitFor, cleanup } from '@testing-library/svelte';
3
-
4
- // Mock the config
5
- vi.mock('../../lib/config.js', () => ({
6
- PUBLIC_GOOGLE_MAPS_API_KEY: 'mock-api-key',
7
- }));
1
+ import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expect, describe, test, vi, beforeEach, afterEach } from 'vitest';
4
+ import PlaceAutocomplete from './PlaceAutocomplete.svelte';
8
5
 
9
6
  // Mock Google Maps API
10
- const mockAutocompleteSessionToken = vi.fn().mockImplementation(() => ({}));
11
- const mockFetchAutocompleteSuggestions = vi.fn().mockResolvedValue({
12
- suggestions: [
13
- {
14
- placePrediction: {
15
- text: { toString: () => 'Los Angeles, CA, USA' },
16
- types: ['locality', 'political'],
17
- toPlace: vi.fn().mockReturnValue({
18
- fetchFields: vi.fn().mockResolvedValue({}),
19
- toJSON: vi.fn().mockReturnValue({
20
- text: 'Los Angeles, CA, USA',
21
- formattedAddress: '123 Main St, Los Angeles, CA 90012',
22
- }),
23
- }),
24
- },
25
- },
26
- {
27
- placePrediction: {
28
- text: { toString: () => 'Long Beach, CA, USA' },
29
- types: ['locality', 'political'],
30
- toPlace: vi.fn().mockReturnValue({
31
- fetchFields: vi.fn().mockResolvedValue({}),
32
- toJSON: vi.fn().mockReturnValue({
33
- text: 'Long Beach, CA, USA',
34
- formattedAddress: '456 Beach St, Long Beach, CA 90801',
35
- }),
36
- }),
37
- },
38
- },
39
- ],
40
- });
7
+ const mockAutocompleteSuggestion = {
8
+ fetchAutocompleteSuggestions: vi.fn(),
9
+ };
41
10
 
11
+ const mockAutocompleteSessionToken = vi.fn();
12
+
13
+ const mockLoader = {
14
+ importLibrary: vi.fn().mockResolvedValue({
15
+ AutocompleteSessionToken: mockAutocompleteSessionToken,
16
+ AutocompleteSuggestion: mockAutocompleteSuggestion,
17
+ }),
18
+ };
19
+
20
+ // Mock the Google Maps Loader
42
21
  vi.mock('@googlemaps/js-api-loader', () => ({
43
- Loader: vi.fn().mockImplementation(() => ({
44
- importLibrary: vi.fn().mockResolvedValue({
45
- AutocompleteSessionToken: mockAutocompleteSessionToken,
46
- AutocompleteSuggestion: {
47
- fetchAutocompleteSuggestions: mockFetchAutocompleteSuggestions,
48
- },
49
- }),
50
- })),
22
+ Loader: vi.fn().mockImplementation(() => mockLoader),
51
23
  }));
52
24
 
53
- describe('PlaceAutocomplete', () => {
54
- let PlaceAutocomplete;
25
+ // Mock config
26
+ vi.mock('$lib/config.js', () => ({
27
+ PUBLIC_GOOGLE_MAPS_API_KEY: 'test-api-key',
28
+ }));
55
29
 
56
- beforeAll(async () => {
57
- vi.resetModules();
58
- PlaceAutocomplete = (await import('./PlaceAutocomplete.svelte')).default;
59
- }, 60000);
30
+ function setupTest(props = {}) {
31
+ const user = userEvent.setup();
32
+ const result = render(PlaceAutocomplete, { props });
33
+ return { user, container: result.container };
34
+ }
60
35
 
36
+ describe('PlaceAutocomplete Component', () => {
61
37
  beforeEach(() => {
62
38
  vi.clearAllMocks();
39
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
40
+ suggestions: [],
41
+ });
63
42
  });
64
43
 
65
44
  afterEach(() => {
66
- cleanup();
45
+ vi.clearAllMocks();
67
46
  });
68
47
 
69
- describe('Basic Rendering', () => {
70
- it('renders the component', async () => {
71
- const { container } = render(PlaceAutocomplete);
72
- expect(container.querySelector('.place-wrapper')).toBeDefined();
48
+ describe('Rendering', () => {
49
+ test('renders input element', () => {
50
+ setupTest();
51
+ const input = screen.getByRole('textbox');
52
+ expect(input).toBeInTheDocument();
73
53
  });
74
54
 
75
- it('renders with default placeholder', async () => {
76
- const { container } = render(PlaceAutocomplete);
77
- const input = container.querySelector('input');
78
- expect(input?.placeholder).toContain('Search');
55
+ test('renders with default placeholder', async () => {
56
+ setupTest();
57
+ await waitFor(() => {
58
+ const input = screen.getByRole('textbox');
59
+ expect(input).toHaveAttribute('placeholder', 'Search for location...');
60
+ });
79
61
  });
80
62
 
81
- it('renders with custom placeholder', async () => {
82
- const { container } = render(PlaceAutocomplete, {
83
- props: { placeholder: 'Enter address' },
84
- });
85
- const input = container.querySelector('input');
86
- expect(input?.placeholder).toBe('Enter address');
63
+ test('renders with custom placeholder', () => {
64
+ setupTest({ placeholder: 'Find a venue...' });
65
+ const input = screen.getByRole('textbox');
66
+ expect(input).toHaveAttribute('placeholder', 'Find a venue...');
67
+ });
68
+
69
+ test('renders search icon', () => {
70
+ const { container } = setupTest();
71
+ const icon = container.querySelector('svg');
72
+ expect(icon).toBeInTheDocument();
73
+ });
74
+
75
+ test('applies wrapper styles correctly', () => {
76
+ const { container } = setupTest();
77
+ const wrapper = container.querySelector('.relative.rounded-lg');
78
+ expect(wrapper).toBeInTheDocument();
87
79
  });
88
80
 
89
- it('renders search icon', async () => {
90
- const { container } = render(PlaceAutocomplete);
91
- expect(container.querySelector('svg')).toBeDefined();
81
+ test('input has correct name attribute', () => {
82
+ setupTest();
83
+ const input = screen.getByRole('textbox');
84
+ expect(input).toHaveAttribute('name', 'location');
92
85
  });
93
86
  });
94
87
 
95
- describe('Props', () => {
96
- it('accepts initialValue prop', async () => {
97
- const { container } = render(PlaceAutocomplete, {
98
- props: { initialValue: 'New York, NY' },
88
+ describe('Initial Value', () => {
89
+ test('displays initial value', async () => {
90
+ setupTest({ initialValue: 'New York, NY' });
91
+ await waitFor(() => {
92
+ const input = screen.getByRole('textbox');
93
+ expect(input.value).toBe('New York, NY');
99
94
  });
100
- const input = container.querySelector('input');
101
- expect(input?.value).toBe('New York, NY');
102
95
  });
103
96
 
104
- it('accepts disabled prop', async () => {
105
- const { container } = render(PlaceAutocomplete, {
106
- props: { disabled: true },
107
- });
108
- const input = container.querySelector('input');
109
- expect(input?.disabled).toBe(true);
97
+ test('does not display value when initialValue is empty', () => {
98
+ setupTest({ initialValue: '' });
99
+ const input = screen.getByRole('textbox');
100
+ expect(input.value).toBe('');
110
101
  });
102
+ });
111
103
 
112
- it('accepts mode prop for cityState', async () => {
113
- const { container } = render(PlaceAutocomplete, {
114
- props: { mode: 'cityState' },
115
- });
116
- const input = container.querySelector('input');
117
- expect(input?.placeholder).toContain('city, state');
104
+ describe('Disabled State', () => {
105
+ test('input can be disabled', () => {
106
+ setupTest({ disabled: true });
107
+ const input = screen.getByRole('textbox');
108
+ expect(input).toBeDisabled();
118
109
  });
119
110
 
120
- it('accepts animateFocus prop', async () => {
121
- const { container } = render(PlaceAutocomplete, {
122
- props: { animateFocus: false },
123
- });
124
- expect(container.querySelector('.place-animate-focus')).toBeNull();
111
+ test('input is enabled by default', () => {
112
+ setupTest();
113
+ const input = screen.getByRole('textbox');
114
+ expect(input).not.toBeDisabled();
115
+ });
116
+
117
+ test('applies disabled opacity class', () => {
118
+ setupTest({ disabled: true });
119
+ const input = screen.getByRole('textbox');
120
+ expect(input).toHaveClass('disabled:opacity-50');
121
+ });
122
+ });
123
+
124
+ describe('Animation', () => {
125
+ test('applies animation class when animateFocus is true', () => {
126
+ const { container } = setupTest({ animateFocus: true });
127
+ const wrapper = container.querySelector('.transition-transform');
128
+ expect(wrapper).toBeInTheDocument();
129
+ expect(wrapper).toHaveClass('focus-within:scale-[1.01]');
130
+ });
131
+
132
+ test('does not apply animation when animateFocus is false', () => {
133
+ const { container } = setupTest({ animateFocus: false });
134
+ const wrapper = container.querySelector('.transition-transform');
135
+ expect(wrapper).not.toBeInTheDocument();
136
+ });
137
+
138
+ test('does not apply animation when disabled', () => {
139
+ const { container } = setupTest({ animateFocus: true, disabled: true });
140
+ const wrapper = container.querySelector('.transition-transform');
141
+ expect(wrapper).not.toBeInTheDocument();
142
+ });
143
+ });
144
+
145
+ describe('Mode Variants', () => {
146
+ test('default mode is full', () => {
147
+ setupTest();
148
+ const input = screen.getByRole('textbox');
149
+ expect(input).toHaveAttribute('placeholder', 'Search for location...');
125
150
  });
126
151
 
127
- it('accepts autocomplete prop', async () => {
128
- const { container } = render(PlaceAutocomplete, {
129
- props: { autocomplete: 'off' },
152
+ test('cityState mode updates placeholder', async () => {
153
+ setupTest({ mode: 'cityState' });
154
+ await waitFor(() => {
155
+ const input = screen.getByRole('textbox');
156
+ expect(input).toHaveAttribute('placeholder', 'Search for city, state...');
130
157
  });
131
- const input = container.querySelector('input');
132
- expect(input?.autocomplete).toBe('off');
158
+ });
159
+
160
+ test('cityState mode with custom placeholder keeps custom value', () => {
161
+ setupTest({ mode: 'cityState', placeholder: 'Custom placeholder' });
162
+ const input = screen.getByRole('textbox');
163
+ expect(input).toHaveAttribute('placeholder', 'Custom placeholder');
164
+ });
165
+ });
166
+
167
+ describe('Autocomplete Attribute', () => {
168
+ test('has autocomplete off by default', () => {
169
+ setupTest();
170
+ const input = screen.getByRole('textbox');
171
+ expect(input).toHaveAttribute('autocomplete', 'off');
172
+ });
173
+
174
+ test('can set autocomplete to on', () => {
175
+ setupTest({ autocomplete: 'on' });
176
+ const input = screen.getByRole('textbox');
177
+ expect(input).toHaveAttribute('autocomplete', 'on');
178
+ });
179
+ });
180
+
181
+ describe('User Input', () => {
182
+ test('updates input value on typing', async () => {
183
+ const { user } = setupTest();
184
+ const input = screen.getByRole('textbox');
185
+
186
+ await user.type(input, 'Los Angeles');
187
+ expect(input.value).toBe('Los Angeles');
188
+ });
189
+
190
+ test('clears input when empty value is typed', async () => {
191
+ const { user } = setupTest({ initialValue: 'Test' });
192
+ const input = screen.getByRole('textbox');
193
+
194
+ await user.clear(input);
195
+ expect(input.value).toBe('');
133
196
  });
134
197
  });
135
198
 
136
- describe('Input Interaction', () => {
137
- it('updates value on input', async () => {
138
- const { container } = render(PlaceAutocomplete);
139
- const input = container.querySelector('input');
199
+ describe('Autocomplete Suggestions', () => {
200
+ test('displays suggestions when typing', async () => {
201
+ const mockSuggestions = [
202
+ {
203
+ placePrediction: {
204
+ text: { toString: () => 'Los Angeles, CA, USA' },
205
+ toPlace: () => ({ fetchFields: vi.fn() }),
206
+ },
207
+ },
208
+ {
209
+ placePrediction: {
210
+ text: { toString: () => 'Los Angeles County, CA, USA' },
211
+ toPlace: () => ({ fetchFields: vi.fn() }),
212
+ },
213
+ },
214
+ ];
140
215
 
141
- await fireEvent.input(input, { target: { value: 'Los' } });
142
- expect(input?.value).toBe('Los');
216
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
217
+ suggestions: mockSuggestions,
218
+ });
219
+
220
+ const { user } = setupTest();
221
+ const input = screen.getByRole('textbox');
222
+
223
+ await user.type(input, 'Los Angeles');
224
+
225
+ await waitFor(() => {
226
+ const options = screen.getByRole('list', { id: 'options' });
227
+ expect(options).toBeInTheDocument();
228
+ });
229
+ });
230
+
231
+ test('does not display suggestions list when results are empty', async () => {
232
+ const { user } = setupTest();
233
+ const input = screen.getByRole('textbox');
234
+
235
+ await user.type(input, 'xyz');
236
+
237
+ const options = screen.queryByRole('list');
238
+ expect(options).not.toBeInTheDocument();
239
+ });
240
+
241
+ test('filters cityState results correctly', async () => {
242
+ const mockSuggestions = [
243
+ {
244
+ placePrediction: {
245
+ text: { toString: () => 'Los Angeles, CA, USA' },
246
+ types: ['locality', 'political'],
247
+ toPlace: () => ({ fetchFields: vi.fn() }),
248
+ },
249
+ },
250
+ {
251
+ placePrediction: {
252
+ text: { toString: () => 'California, USA' },
253
+ types: ['administrative_area_level_1', 'political'],
254
+ toPlace: () => ({ fetchFields: vi.fn() }),
255
+ },
256
+ },
257
+ {
258
+ placePrediction: {
259
+ text: { toString: () => 'Van Nuys, Los Angeles, CA, USA' },
260
+ types: ['sublocality', 'political'],
261
+ toPlace: () => ({ fetchFields: vi.fn() }),
262
+ },
263
+ },
264
+ ];
265
+
266
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
267
+ suggestions: mockSuggestions,
268
+ });
269
+
270
+ const { user } = setupTest({ mode: 'cityState' });
271
+ const input = screen.getByRole('textbox');
272
+
273
+ await user.type(input, 'Los Angeles');
274
+
275
+ await waitFor(() => {
276
+ const buttons = screen.getAllByRole('button');
277
+ // Should have 2 results: Los Angeles and Van Nuys (not California)
278
+ expect(buttons).toHaveLength(2);
279
+ });
143
280
  });
144
281
 
145
- it('clears results on empty input', async () => {
146
- const { container } = render(PlaceAutocomplete);
147
- const input = container.querySelector('input');
282
+ test('formats cityState results to show first two parts', async () => {
283
+ const mockSuggestions = [
284
+ {
285
+ placePrediction: {
286
+ text: { toString: () => 'Los Angeles, CA, USA' },
287
+ types: ['locality'],
288
+ toPlace: () => ({ fetchFields: vi.fn() }),
289
+ },
290
+ },
291
+ ];
292
+
293
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
294
+ suggestions: mockSuggestions,
295
+ });
296
+
297
+ const { user } = setupTest({ mode: 'cityState' });
298
+ const input = screen.getByRole('textbox');
299
+
300
+ await user.type(input, 'Los Angeles');
148
301
 
149
- await fireEvent.input(input, { target: { value: 'Los' } });
150
- await fireEvent.input(input, { target: { value: '' } });
151
- expect(input?.value).toBe('');
302
+ await waitFor(() => {
303
+ const button = screen.getByText('Los Angeles, CA');
304
+ expect(button).toBeInTheDocument();
305
+ });
152
306
  });
153
307
  });
154
308
 
155
309
  describe('Keyboard Navigation', () => {
156
- it('handles Escape key', async () => {
157
- const { container } = render(PlaceAutocomplete);
158
- const input = container.querySelector('input');
310
+ beforeEach(() => {
311
+ const mockSuggestions = [
312
+ {
313
+ placePrediction: {
314
+ text: { toString: () => 'Result 1' },
315
+ toPlace: () => ({
316
+ fetchFields: vi.fn().mockResolvedValue({}),
317
+ toJSON: () => ({ text: 'Result 1' }),
318
+ }),
319
+ },
320
+ },
321
+ {
322
+ placePrediction: {
323
+ text: { toString: () => 'Result 2' },
324
+ toPlace: () => ({
325
+ fetchFields: vi.fn().mockResolvedValue({}),
326
+ toJSON: () => ({ text: 'Result 2' }),
327
+ }),
328
+ },
329
+ },
330
+ {
331
+ placePrediction: {
332
+ text: { toString: () => 'Result 3' },
333
+ toPlace: () => ({
334
+ fetchFields: vi.fn().mockResolvedValue({}),
335
+ toJSON: () => ({ text: 'Result 3' }),
336
+ }),
337
+ },
338
+ },
339
+ ];
159
340
 
160
- await fireEvent.keyDown(input, { key: 'Escape' });
161
- expect(container).toBeDefined();
341
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
342
+ suggestions: mockSuggestions,
343
+ });
344
+ });
345
+
346
+ test('ArrowDown highlights first suggestion', async () => {
347
+ const { user, container } = setupTest();
348
+ const input = screen.getByRole('textbox');
349
+
350
+ await user.type(input, 'Test');
351
+
352
+ await waitFor(() => {
353
+ expect(screen.getByRole('list')).toBeInTheDocument();
354
+ });
355
+
356
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
357
+
358
+ await waitFor(() => {
359
+ const highlighted = container.querySelector('.bg-gray-100');
360
+ expect(highlighted).toBeInTheDocument();
361
+ });
162
362
  });
163
363
 
164
- it('handles ArrowDown key with no results', async () => {
165
- const { container } = render(PlaceAutocomplete);
166
- const input = container.querySelector('input');
364
+ test('ArrowDown cycles through suggestions', async () => {
365
+ const { user, container } = setupTest();
366
+ const input = screen.getByRole('textbox');
367
+
368
+ await user.type(input, 'Test');
369
+
370
+ await waitFor(() => {
371
+ expect(screen.getByRole('list')).toBeInTheDocument();
372
+ });
373
+
374
+ // Press down three times
375
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
376
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
377
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
167
378
 
379
+ // Should cycle back to first
168
380
  await fireEvent.keyDown(input, { key: 'ArrowDown' });
169
- expect(container).toBeDefined();
381
+
382
+ const highlighted = container.querySelectorAll('.bg-gray-100');
383
+ expect(highlighted.length).toBeGreaterThan(0);
170
384
  });
171
385
 
172
- it('handles ArrowUp key with no results', async () => {
173
- const { container } = render(PlaceAutocomplete);
174
- const input = container.querySelector('input');
386
+ test('ArrowUp highlights last suggestion when none selected', async () => {
387
+ const { user, container } = setupTest();
388
+ const input = screen.getByRole('textbox');
389
+
390
+ await user.type(input, 'Test');
175
391
 
392
+ await waitFor(() => {
393
+ expect(screen.getByRole('list')).toBeInTheDocument();
394
+ });
395
+
396
+ await fireEvent.keyDown(input, { key: 'ArrowUp' });
397
+
398
+ const highlighted = container.querySelector('.bg-gray-100');
399
+ expect(highlighted).toBeInTheDocument();
400
+ });
401
+
402
+ test('ArrowUp moves selection upward', async () => {
403
+ const { user, container } = setupTest();
404
+ const input = screen.getByRole('textbox');
405
+
406
+ await user.type(input, 'Test');
407
+
408
+ await waitFor(() => {
409
+ expect(screen.getByRole('list')).toBeInTheDocument();
410
+ });
411
+
412
+ // Go down twice, then up once
413
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
414
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
176
415
  await fireEvent.keyDown(input, { key: 'ArrowUp' });
177
- expect(container).toBeDefined();
416
+
417
+ // Should be on first suggestion - check the li element
418
+ await waitFor(() => {
419
+ const highlighted = container.querySelector('li.bg-gray-100');
420
+ expect(highlighted).toBeInTheDocument();
421
+ });
422
+ });
423
+
424
+ test('Enter selects highlighted suggestion', async () => {
425
+ const onResponse = vi.fn();
426
+ const { user } = setupTest({ onResponse });
427
+ const input = screen.getByRole('textbox');
428
+
429
+ await user.type(input, 'Test');
430
+
431
+ await waitFor(() => {
432
+ expect(screen.getByRole('list')).toBeInTheDocument();
433
+ });
434
+
435
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
436
+ await fireEvent.keyDown(input, { key: 'Enter' });
437
+
438
+ await waitFor(() => {
439
+ expect(onResponse).toHaveBeenCalled();
440
+ });
178
441
  });
179
442
 
180
- it('handles Enter key with no selection', async () => {
181
- const { container } = render(PlaceAutocomplete);
182
- const input = container.querySelector('input');
443
+ test('Enter does nothing when no suggestion is highlighted', async () => {
444
+ const onResponse = vi.fn();
445
+ const { user } = setupTest({ onResponse });
446
+ const input = screen.getByRole('textbox');
447
+
448
+ await user.type(input, 'Test');
449
+
450
+ await waitFor(() => {
451
+ expect(screen.getByRole('list')).toBeInTheDocument();
452
+ });
183
453
 
184
454
  await fireEvent.keyDown(input, { key: 'Enter' });
185
- expect(container).toBeDefined();
455
+
456
+ expect(onResponse).not.toHaveBeenCalled();
457
+ });
458
+
459
+ test('Escape clears results', async () => {
460
+ const { user } = setupTest();
461
+ const input = screen.getByRole('textbox');
462
+
463
+ await user.type(input, 'Test');
464
+
465
+ await waitFor(() => {
466
+ expect(screen.getByRole('list')).toBeInTheDocument();
467
+ });
468
+
469
+ await fireEvent.keyDown(input, { key: 'Escape' });
470
+
471
+ await waitFor(() => {
472
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
473
+ });
474
+ });
475
+
476
+ test('keyboard navigation does nothing when no results', async () => {
477
+ const { user } = setupTest();
478
+ const input = screen.getByRole('textbox');
479
+
480
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
481
+ await fireEvent.keyDown(input, { key: 'ArrowUp' });
482
+ await fireEvent.keyDown(input, { key: 'Enter' });
483
+
484
+ // Should not throw error
485
+ expect(input).toBeInTheDocument();
186
486
  });
187
487
  });
188
488
 
189
- describe('Callbacks', () => {
190
- it('calls onResponse when provided', async () => {
489
+ describe('Place Selection', () => {
490
+ test('clicking suggestion calls onResponse', async () => {
191
491
  const onResponse = vi.fn();
192
- const { container } = render(PlaceAutocomplete, {
193
- props: { onResponse },
492
+ const mockPlace = {
493
+ fetchFields: vi.fn().mockResolvedValue({}),
494
+ toJSON: () => ({ formattedAddress: '123 Main St', text: 'Test Place' }),
495
+ };
496
+
497
+ const mockSuggestions = [
498
+ {
499
+ placePrediction: {
500
+ text: { toString: () => 'Test Place' },
501
+ toPlace: () => mockPlace,
502
+ },
503
+ },
504
+ ];
505
+
506
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
507
+ suggestions: mockSuggestions,
508
+ });
509
+
510
+ const { user } = setupTest({ onResponse });
511
+ const input = screen.getByRole('textbox');
512
+
513
+ await user.type(input, 'Test');
514
+
515
+ await waitFor(() => {
516
+ expect(screen.getByRole('list')).toBeInTheDocument();
517
+ });
518
+
519
+ const button = screen.getByRole('button', { name: /Test Place/i });
520
+ await fireEvent.click(button);
521
+
522
+ await waitFor(() => {
523
+ expect(onResponse).toHaveBeenCalledWith(
524
+ expect.objectContaining({ text: 'Test Place' })
525
+ );
194
526
  });
195
- expect(container).toBeDefined();
196
527
  });
197
528
 
198
- it('calls onError when provided', async () => {
529
+ test('selecting place updates input value', async () => {
530
+ const mockPlace = {
531
+ fetchFields: vi.fn().mockResolvedValue({}),
532
+ toJSON: () => ({ text: 'Selected Place' }),
533
+ };
534
+
535
+ const mockSuggestions = [
536
+ {
537
+ placePrediction: {
538
+ text: { toString: () => 'Selected Place' },
539
+ toPlace: () => mockPlace,
540
+ },
541
+ },
542
+ ];
543
+
544
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
545
+ suggestions: mockSuggestions,
546
+ });
547
+
548
+ const { user } = setupTest();
549
+ const input = screen.getByRole('textbox');
550
+
551
+ await user.type(input, 'Test');
552
+
553
+ await waitFor(() => {
554
+ expect(screen.getByRole('list')).toBeInTheDocument();
555
+ });
556
+
557
+ const button = screen.getByRole('button');
558
+ await fireEvent.click(button);
559
+
560
+ await waitFor(() => {
561
+ expect(input.value).toBe('Selected Place');
562
+ });
563
+ });
564
+
565
+ test('selecting place clears results list', async () => {
566
+ const mockPlace = {
567
+ fetchFields: vi.fn().mockResolvedValue({}),
568
+ toJSON: () => ({ text: 'Selected Place' }),
569
+ };
570
+
571
+ const mockSuggestions = [
572
+ {
573
+ placePrediction: {
574
+ text: { toString: () => 'Selected Place' },
575
+ toPlace: () => mockPlace,
576
+ },
577
+ },
578
+ ];
579
+
580
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
581
+ suggestions: mockSuggestions,
582
+ });
583
+
584
+ const { user } = setupTest();
585
+ const input = screen.getByRole('textbox');
586
+
587
+ await user.type(input, 'Test');
588
+
589
+ await waitFor(() => {
590
+ expect(screen.getByRole('list')).toBeInTheDocument();
591
+ });
592
+
593
+ const button = screen.getByRole('button');
594
+ await fireEvent.click(button);
595
+
596
+ await waitFor(() => {
597
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
598
+ });
599
+ });
600
+
601
+ test('uses originalText for cityState mode', async () => {
602
+ const onResponse = vi.fn();
603
+ const mockPlace = {
604
+ fetchFields: vi.fn().mockResolvedValue({}),
605
+ toJSON: () => ({ text: 'Los Angeles, CA' }),
606
+ };
607
+
608
+ const mockSuggestions = [
609
+ {
610
+ placePrediction: {
611
+ text: { toString: () => 'Los Angeles, CA, USA' },
612
+ types: ['locality'],
613
+ toPlace: () => mockPlace,
614
+ },
615
+ },
616
+ ];
617
+
618
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
619
+ suggestions: mockSuggestions,
620
+ });
621
+
622
+ const { user } = setupTest({ mode: 'cityState', onResponse });
623
+ const input = screen.getByRole('textbox');
624
+
625
+ await user.type(input, 'Los Angeles');
626
+
627
+ await waitFor(() => {
628
+ expect(screen.getByRole('list')).toBeInTheDocument();
629
+ });
630
+
631
+ const button = screen.getByRole('button');
632
+ await fireEvent.click(button);
633
+
634
+ await waitFor(() => {
635
+ expect(input.value).toBe('Los Angeles, CA');
636
+ });
637
+ });
638
+ });
639
+
640
+ describe('Click Outside', () => {
641
+ test('clicking outside clears results', async () => {
642
+ const mockSuggestions = [
643
+ {
644
+ placePrediction: {
645
+ text: { toString: () => 'Test Place' },
646
+ toPlace: () => ({ fetchFields: vi.fn() }),
647
+ },
648
+ },
649
+ ];
650
+
651
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
652
+ suggestions: mockSuggestions,
653
+ });
654
+
655
+ const { user } = setupTest();
656
+ const input = screen.getByRole('textbox');
657
+
658
+ await user.type(input, 'Test');
659
+
660
+ await waitFor(() => {
661
+ expect(screen.getByRole('list')).toBeInTheDocument();
662
+ });
663
+
664
+ // Click outside
665
+ await fireEvent.click(document.body);
666
+
667
+ await waitFor(() => {
668
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
669
+ });
670
+ });
671
+
672
+ test('clicking inside does not clear results', async () => {
673
+ const mockSuggestions = [
674
+ {
675
+ placePrediction: {
676
+ text: { toString: () => 'Test Place' },
677
+ toPlace: () => ({ fetchFields: vi.fn() }),
678
+ },
679
+ },
680
+ ];
681
+
682
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
683
+ suggestions: mockSuggestions,
684
+ });
685
+
686
+ const { user, container } = setupTest();
687
+ const input = screen.getByRole('textbox');
688
+
689
+ await user.type(input, 'Test');
690
+
691
+ await waitFor(() => {
692
+ expect(screen.getByRole('list')).toBeInTheDocument();
693
+ });
694
+
695
+ // Click on the input itself
696
+ await fireEvent.click(input);
697
+
698
+ // Results should still be visible
699
+ expect(screen.getByRole('list')).toBeInTheDocument();
700
+ });
701
+ });
702
+
703
+ describe('Error Handling', () => {
704
+ test('calls onError when API request fails', async () => {
199
705
  const onError = vi.fn();
200
- const { container } = render(PlaceAutocomplete, {
201
- props: { onError },
706
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockRejectedValue(
707
+ new Error('API Error')
708
+ );
709
+
710
+ const { user } = setupTest({ onError });
711
+ const input = screen.getByRole('textbox');
712
+
713
+ await user.type(input, 'Test');
714
+
715
+ await waitFor(() => {
716
+ expect(onError).toHaveBeenCalledWith(expect.stringContaining('API Error'));
202
717
  });
203
- expect(container).toBeDefined();
718
+ });
719
+
720
+ test('calls onError when place selection fails', async () => {
721
+ const onError = vi.fn();
722
+ const mockPlace = {
723
+ fetchFields: vi.fn().mockRejectedValue(new Error('Fetch Error')),
724
+ };
725
+
726
+ const mockSuggestions = [
727
+ {
728
+ placePrediction: {
729
+ text: { toString: () => 'Test Place' },
730
+ toPlace: () => mockPlace,
731
+ },
732
+ },
733
+ ];
734
+
735
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
736
+ suggestions: mockSuggestions,
737
+ });
738
+
739
+ const { user } = setupTest({ onError });
740
+ const input = screen.getByRole('textbox');
741
+
742
+ await user.type(input, 'Test');
743
+
744
+ await waitFor(() => {
745
+ expect(screen.getByRole('list')).toBeInTheDocument();
746
+ });
747
+
748
+ const button = screen.getByRole('button');
749
+ await fireEvent.click(button);
750
+
751
+ await waitFor(() => {
752
+ expect(onError).toHaveBeenCalledWith(expect.stringContaining('Fetch Error'));
753
+ });
754
+ });
755
+
756
+ test('handles missing API key gracefully', async () => {
757
+ const onError = vi.fn();
758
+
759
+ // This test verifies the component handles initialization properly
760
+ setupTest({ onError });
761
+
762
+ // Component should initialize without throwing
763
+ const input = screen.getByRole('textbox');
764
+ expect(input).toBeInTheDocument();
204
765
  });
205
766
  });
206
767
 
207
- describe('Styling', () => {
208
- it('has correct wrapper class', async () => {
209
- const { container } = render(PlaceAutocomplete);
210
- expect(container.querySelector('.place-wrapper')).toBeDefined();
768
+ describe('Keyboard Hints', () => {
769
+ test('displays keyboard hints when results are visible', async () => {
770
+ const mockSuggestions = [
771
+ {
772
+ placePrediction: {
773
+ text: { toString: () => 'Test Place' },
774
+ toPlace: () => ({ fetchFields: vi.fn() }),
775
+ },
776
+ },
777
+ ];
778
+
779
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
780
+ suggestions: mockSuggestions,
781
+ });
782
+
783
+ const { user, container } = setupTest();
784
+ const input = screen.getByRole('textbox');
785
+
786
+ await user.type(input, 'Test');
787
+
788
+ await waitFor(() => {
789
+ expect(screen.getByRole('list')).toBeInTheDocument();
790
+ });
791
+
792
+ // Check for keyboard hints
793
+ const hints = container.querySelectorAll('kbd');
794
+ expect(hints.length).toBeGreaterThan(0);
211
795
  });
212
796
 
213
- it('has correct container class', async () => {
214
- const { container } = render(PlaceAutocomplete);
215
- expect(container.querySelector('.place-container')).toBeDefined();
797
+ test('does not display hints when no results', () => {
798
+ const { container } = setupTest();
799
+ const hints = container.querySelectorAll('kbd');
800
+ expect(hints.length).toBe(0);
216
801
  });
802
+ });
803
+
804
+ describe('Fetch Fields Configuration', () => {
805
+ test('uses default fetch fields', async () => {
806
+ const mockPlace = {
807
+ fetchFields: vi.fn().mockResolvedValue({}),
808
+ toJSON: () => ({ text: 'Test' }),
809
+ };
810
+
811
+ const mockSuggestions = [
812
+ {
813
+ placePrediction: {
814
+ text: { toString: () => 'Test' },
815
+ toPlace: () => mockPlace,
816
+ },
817
+ },
818
+ ];
819
+
820
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
821
+ suggestions: mockSuggestions,
822
+ });
823
+
824
+ const { user } = setupTest();
825
+ const input = screen.getByRole('textbox');
826
+
827
+ await user.type(input, 'Test');
828
+
829
+ await waitFor(() => {
830
+ expect(screen.getByRole('list')).toBeInTheDocument();
831
+ });
832
+
833
+ const button = screen.getByRole('button');
834
+ await fireEvent.click(button);
835
+
836
+ await waitFor(() => {
837
+ expect(mockPlace.fetchFields).toHaveBeenCalledWith({
838
+ fields: ['formattedAddress', 'addressComponents'],
839
+ });
840
+ });
841
+ });
842
+
843
+ test('uses custom fetch fields', async () => {
844
+ const mockPlace = {
845
+ fetchFields: vi.fn().mockResolvedValue({}),
846
+ toJSON: () => ({ text: 'Test' }),
847
+ };
848
+
849
+ const mockSuggestions = [
850
+ {
851
+ placePrediction: {
852
+ text: { toString: () => 'Test' },
853
+ toPlace: () => mockPlace,
854
+ },
855
+ },
856
+ ];
857
+
858
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
859
+ suggestions: mockSuggestions,
860
+ });
861
+
862
+ const { user } = setupTest({ fetchFields: ['location', 'displayName'] });
863
+ const input = screen.getByRole('textbox');
864
+
865
+ await user.type(input, 'Test');
866
+
867
+ await waitFor(() => {
868
+ expect(screen.getByRole('list')).toBeInTheDocument();
869
+ });
870
+
871
+ const button = screen.getByRole('button');
872
+ await fireEvent.click(button);
217
873
 
218
- it('has correct input class', async () => {
219
- const { container } = render(PlaceAutocomplete);
220
- expect(container.querySelector('.place-input')).toBeDefined();
874
+ await waitFor(() => {
875
+ expect(mockPlace.fetchFields).toHaveBeenCalledWith({
876
+ fields: ['location', 'displayName'],
877
+ });
878
+ });
221
879
  });
880
+ });
222
881
 
223
- it('applies animate focus class when enabled', async () => {
224
- const { container } = render(PlaceAutocomplete, {
225
- props: { animateFocus: true, disabled: false },
882
+ describe('Language and Region', () => {
883
+ test('uses default language and region', async () => {
884
+ const { user } = setupTest();
885
+ const input = screen.getByRole('textbox');
886
+
887
+ await user.type(input, 'Test');
888
+
889
+ await waitFor(() => {
890
+ expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).toHaveBeenCalledWith(
891
+ expect.objectContaining({
892
+ language: 'en-US',
893
+ region: 'US',
894
+ })
895
+ );
226
896
  });
227
- expect(container.querySelector('.place-animate-focus')).toBeDefined();
228
897
  });
229
898
 
230
- it('does not apply animate focus class when disabled', async () => {
231
- const { container } = render(PlaceAutocomplete, {
232
- props: { animateFocus: true, disabled: true },
899
+ test('uses custom language and region', async () => {
900
+ const { user } = setupTest({ language: 'es-ES', region: 'ES' });
901
+ const input = screen.getByRole('textbox');
902
+
903
+ await user.type(input, 'Test');
904
+
905
+ await waitFor(() => {
906
+ expect(mockAutocompleteSuggestion.fetchAutocompleteSuggestions).toHaveBeenCalledWith(
907
+ expect.objectContaining({
908
+ language: 'es-ES',
909
+ region: 'ES',
910
+ })
911
+ );
233
912
  });
234
- expect(container.querySelector('.place-animate-focus')).toBeNull();
235
913
  });
236
914
  });
237
915
 
238
- describe('Mode: cityState', () => {
239
- it('filters results to only localities', async () => {
240
- const { container } = render(PlaceAutocomplete, {
241
- props: { mode: 'cityState' },
916
+ describe('Input Styling', () => {
917
+ test('has correct base classes', () => {
918
+ setupTest();
919
+ const input = screen.getByRole('textbox');
920
+ expect(input).toHaveClass('block');
921
+ expect(input).toHaveClass('w-full');
922
+ expect(input).toHaveClass('rounded-lg');
923
+ expect(input).toHaveClass('bg-gray-50');
924
+ expect(input).toHaveClass('border');
925
+ expect(input).toHaveClass('border-gray-300');
926
+ });
927
+
928
+ test('has focus styles', () => {
929
+ setupTest();
930
+ const input = screen.getByRole('textbox');
931
+ expect(input).toHaveClass('focus:ring-blue-500');
932
+ expect(input).toHaveClass('focus:border-blue-500');
933
+ });
934
+
935
+ test('has hover styles', () => {
936
+ setupTest();
937
+ const input = screen.getByRole('textbox');
938
+ expect(input).toHaveClass('hover:border-blue-500');
939
+ });
940
+
941
+ test('icon has correct positioning', () => {
942
+ const { container } = setupTest();
943
+ const iconContainer = container.querySelector('.pointer-events-none.absolute');
944
+ expect(iconContainer).toBeInTheDocument();
945
+ expect(iconContainer).toHaveClass('inset-y-0');
946
+ expect(iconContainer).toHaveClass('left-0');
947
+ });
948
+ });
949
+
950
+ describe('Results List Styling', () => {
951
+ test('results list has correct styling', async () => {
952
+ const mockSuggestions = [
953
+ {
954
+ placePrediction: {
955
+ text: { toString: () => 'Test Place' },
956
+ toPlace: () => ({ fetchFields: vi.fn() }),
957
+ },
958
+ },
959
+ ];
960
+
961
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
962
+ suggestions: mockSuggestions,
963
+ });
964
+
965
+ const { user } = setupTest();
966
+ const input = screen.getByRole('textbox');
967
+
968
+ await user.type(input, 'Test');
969
+
970
+ await waitFor(() => {
971
+ const list = screen.getByRole('list');
972
+ expect(list).toHaveClass('absolute');
973
+ expect(list).toHaveClass('top-full');
974
+ expect(list).toHaveClass('rounded-lg');
975
+ expect(list).toHaveClass('shadow-lg');
976
+ expect(list).toHaveClass('bg-white');
242
977
  });
243
- expect(container).toBeDefined();
244
978
  });
245
979
 
246
- it('updates placeholder for cityState mode', async () => {
247
- const { container } = render(PlaceAutocomplete, {
248
- props: { mode: 'cityState' },
980
+ test('result items have hover styling', async () => {
981
+ const mockSuggestions = [
982
+ {
983
+ placePrediction: {
984
+ text: { toString: () => 'Test Place' },
985
+ toPlace: () => ({ fetchFields: vi.fn() }),
986
+ },
987
+ },
988
+ ];
989
+
990
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
991
+ suggestions: mockSuggestions,
992
+ });
993
+
994
+ const { user, container } = setupTest();
995
+ const input = screen.getByRole('textbox');
996
+
997
+ await user.type(input, 'Test');
998
+
999
+ await waitFor(() => {
1000
+ const listItem = container.querySelector('li');
1001
+ expect(listItem).toHaveClass('cursor-pointer');
1002
+ expect(listItem).toHaveClass('transition-colors');
249
1003
  });
250
- const input = container.querySelector('input');
251
- expect(input?.placeholder).toContain('city, state');
252
1004
  });
253
1005
  });
254
1006
 
255
- describe('Error Handling', () => {
256
- it('handles missing API key gracefully', async () => {
257
- const onError = vi.fn();
258
- const { container } = render(PlaceAutocomplete, {
259
- props: { onError },
1007
+ describe('Multiple Suggestions', () => {
1008
+ test('displays all suggestions', async () => {
1009
+ const mockSuggestions = [
1010
+ {
1011
+ placePrediction: {
1012
+ text: { toString: () => 'Result 1' },
1013
+ toPlace: () => ({ fetchFields: vi.fn() }),
1014
+ },
1015
+ },
1016
+ {
1017
+ placePrediction: {
1018
+ text: { toString: () => 'Result 2' },
1019
+ toPlace: () => ({ fetchFields: vi.fn() }),
1020
+ },
1021
+ },
1022
+ {
1023
+ placePrediction: {
1024
+ text: { toString: () => 'Result 3' },
1025
+ toPlace: () => ({ fetchFields: vi.fn() }),
1026
+ },
1027
+ },
1028
+ {
1029
+ placePrediction: {
1030
+ text: { toString: () => 'Result 4' },
1031
+ toPlace: () => ({ fetchFields: vi.fn() }),
1032
+ },
1033
+ },
1034
+ {
1035
+ placePrediction: {
1036
+ text: { toString: () => 'Result 5' },
1037
+ toPlace: () => ({ fetchFields: vi.fn() }),
1038
+ },
1039
+ },
1040
+ ];
1041
+
1042
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1043
+ suggestions: mockSuggestions,
1044
+ });
1045
+
1046
+ const { user } = setupTest();
1047
+ const input = screen.getByRole('textbox');
1048
+
1049
+ await user.type(input, 'Test');
1050
+
1051
+ await waitFor(() => {
1052
+ const buttons = screen.getAllByRole('button');
1053
+ expect(buttons).toHaveLength(5);
1054
+ });
1055
+ });
1056
+
1057
+ test('each suggestion has correct id attribute', async () => {
1058
+ const mockSuggestions = [
1059
+ {
1060
+ placePrediction: {
1061
+ text: { toString: () => 'Result 1' },
1062
+ toPlace: () => ({ fetchFields: vi.fn() }),
1063
+ },
1064
+ },
1065
+ {
1066
+ placePrediction: {
1067
+ text: { toString: () => 'Result 2' },
1068
+ toPlace: () => ({ fetchFields: vi.fn() }),
1069
+ },
1070
+ },
1071
+ ];
1072
+
1073
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1074
+ suggestions: mockSuggestions,
1075
+ });
1076
+
1077
+ const { user, container } = setupTest();
1078
+ const input = screen.getByRole('textbox');
1079
+
1080
+ await user.type(input, 'Test');
1081
+
1082
+ await waitFor(() => {
1083
+ expect(container.querySelector('#option-1')).toBeInTheDocument();
1084
+ expect(container.querySelector('#option-2')).toBeInTheDocument();
260
1085
  });
261
- expect(container).toBeDefined();
262
1086
  });
263
1087
  });
264
1088
 
265
- describe('Click Outside', () => {
266
- it('handles click outside to close dropdown', async () => {
267
- const { container } = render(PlaceAutocomplete);
268
-
269
- // Simulate click outside
270
- document.body.click();
271
-
1089
+ describe('Integration Tests', () => {
1090
+ test('complete flow: type, navigate, select', async () => {
1091
+ const onResponse = vi.fn();
1092
+ const mockPlace = {
1093
+ fetchFields: vi.fn().mockResolvedValue({}),
1094
+ toJSON: () => ({ text: 'Final Selection', formattedAddress: '123 Main St' }),
1095
+ };
1096
+
1097
+ const mockSuggestions = [
1098
+ {
1099
+ placePrediction: {
1100
+ text: { toString: () => 'Result 1' },
1101
+ toPlace: () => mockPlace,
1102
+ },
1103
+ },
1104
+ {
1105
+ placePrediction: {
1106
+ text: { toString: () => 'Final Selection' },
1107
+ toPlace: () => mockPlace,
1108
+ },
1109
+ },
1110
+ ];
1111
+
1112
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1113
+ suggestions: mockSuggestions,
1114
+ });
1115
+
1116
+ const { user } = setupTest({ onResponse });
1117
+ const input = screen.getByRole('textbox');
1118
+
1119
+ // Type to get suggestions
1120
+ await user.type(input, 'Test');
1121
+
1122
+ await waitFor(() => {
1123
+ expect(screen.getByRole('list')).toBeInTheDocument();
1124
+ });
1125
+
1126
+ // Navigate down to second item
1127
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
1128
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
1129
+
1130
+ // Select with Enter
1131
+ await fireEvent.keyDown(input, { key: 'Enter' });
1132
+
272
1133
  await waitFor(() => {
273
- expect(container.querySelector('.place-dropdown')).toBeNull();
1134
+ expect(onResponse).toHaveBeenCalledWith(
1135
+ expect.objectContaining({ text: 'Final Selection' })
1136
+ );
1137
+ expect(input.value).toBe('Final Selection');
1138
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
1139
+ });
1140
+ });
1141
+
1142
+ test('complete flow with cityState mode', async () => {
1143
+ const onResponse = vi.fn();
1144
+ const mockPlace = {
1145
+ fetchFields: vi.fn().mockResolvedValue({}),
1146
+ toJSON: () => ({ text: 'Los Angeles, CA' }),
1147
+ };
1148
+
1149
+ const mockSuggestions = [
1150
+ {
1151
+ placePrediction: {
1152
+ text: { toString: () => 'Los Angeles, CA, USA' },
1153
+ types: ['locality'],
1154
+ toPlace: () => mockPlace,
1155
+ },
1156
+ },
1157
+ ];
1158
+
1159
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1160
+ suggestions: mockSuggestions,
1161
+ });
1162
+
1163
+ const { user } = setupTest({ mode: 'cityState', onResponse });
1164
+ const input = screen.getByRole('textbox');
1165
+
1166
+ await user.type(input, 'Los Angeles');
1167
+
1168
+ await waitFor(() => {
1169
+ expect(screen.getByText('Los Angeles, CA')).toBeInTheDocument();
1170
+ });
1171
+
1172
+ const button = screen.getByRole('button');
1173
+ await fireEvent.click(button);
1174
+
1175
+ await waitFor(() => {
1176
+ expect(input.value).toBe('Los Angeles, CA');
1177
+ expect(onResponse).toHaveBeenCalled();
274
1178
  });
275
1179
  });
276
1180
  });
277
1181
 
278
- describe('Initial Value', () => {
279
- it('sets initial value on mount', async () => {
280
- const { container } = render(PlaceAutocomplete, {
281
- props: { initialValue: 'San Francisco, CA' },
1182
+ describe('Accessibility', () => {
1183
+ test('input has aria-controls attribute', () => {
1184
+ setupTest();
1185
+ const input = screen.getByRole('textbox');
1186
+ expect(input).toHaveAttribute('aria-controls', 'options');
1187
+ });
1188
+
1189
+ test('results list has correct id', async () => {
1190
+ const mockSuggestions = [
1191
+ {
1192
+ placePrediction: {
1193
+ text: { toString: () => 'Test Place' },
1194
+ toPlace: () => ({ fetchFields: vi.fn() }),
1195
+ },
1196
+ },
1197
+ ];
1198
+
1199
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1200
+ suggestions: mockSuggestions,
1201
+ });
1202
+
1203
+ const { user } = setupTest();
1204
+ const input = screen.getByRole('textbox');
1205
+
1206
+ await user.type(input, 'Test');
1207
+
1208
+ await waitFor(() => {
1209
+ const list = screen.getByRole('list');
1210
+ expect(list).toHaveAttribute('id', 'options');
282
1211
  });
283
-
284
- const input = container.querySelector('input');
285
- expect(input?.value).toBe('San Francisco, CA');
286
1212
  });
287
1213
 
288
- it('initializes only once', async () => {
289
- const { container, rerender } = render(PlaceAutocomplete, {
290
- props: { initialValue: 'San Francisco, CA' },
1214
+ test('suggestion buttons have correct tabindex', async () => {
1215
+ const mockSuggestions = [
1216
+ {
1217
+ placePrediction: {
1218
+ text: { toString: () => 'Result 1' },
1219
+ toPlace: () => ({ fetchFields: vi.fn() }),
1220
+ },
1221
+ },
1222
+ {
1223
+ placePrediction: {
1224
+ text: { toString: () => 'Result 2' },
1225
+ toPlace: () => ({ fetchFields: vi.fn() }),
1226
+ },
1227
+ },
1228
+ ];
1229
+
1230
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
1231
+ suggestions: mockSuggestions,
291
1232
  });
292
1233
 
293
- await rerender({ initialValue: 'Los Angeles, CA' });
294
-
295
- // Value should remain the initial value
296
- const input = container.querySelector('input');
297
- expect(input).toBeDefined();
1234
+ const { user } = setupTest();
1235
+ const input = screen.getByRole('textbox');
1236
+
1237
+ await user.type(input, 'Test');
1238
+
1239
+ await waitFor(() => {
1240
+ const buttons = screen.getAllByRole('button');
1241
+ expect(buttons[0]).toHaveAttribute('tabindex', '1');
1242
+ expect(buttons[1]).toHaveAttribute('tabindex', '2');
1243
+ });
298
1244
  });
299
1245
  });
300
1246
  });