@getmicdrop/svelte-components 5.5.4 → 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 (290) 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/Calendar/MiniMonthCalendar.spec.d.ts +2 -0
  5. package/dist/calendar/Calendar/MiniMonthCalendar.spec.d.ts.map +1 -0
  6. package/dist/calendar/Calendar/MiniMonthCalendar.spec.js +1191 -0
  7. package/dist/calendar/FAQs/FAQs.spec.d.ts +2 -0
  8. package/dist/calendar/FAQs/FAQs.spec.d.ts.map +1 -0
  9. package/dist/calendar/FAQs/FAQs.spec.js +238 -0
  10. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts +2 -0
  11. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.d.ts.map +1 -0
  12. package/dist/calendar/MonthSwitcher/MonthSwitcher.spec.js +420 -0
  13. package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts +2 -0
  14. package/dist/calendar/OrderSummary/OrderSummary.spec.d.ts.map +1 -0
  15. package/dist/calendar/OrderSummary/OrderSummary.spec.js +808 -0
  16. package/dist/calendar/PublicCard/PublicCard.spec.d.ts +2 -0
  17. package/dist/calendar/PublicCard/PublicCard.spec.d.ts.map +1 -0
  18. package/dist/calendar/PublicCard/PublicCard.spec.js +301 -0
  19. package/dist/calendar/ShowCard/ShowCard.spec.d.ts +2 -0
  20. package/dist/calendar/ShowCard/ShowCard.spec.d.ts.map +1 -0
  21. package/dist/calendar/ShowCard/ShowCard.spec.js +714 -0
  22. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts +2 -0
  23. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.d.ts.map +1 -0
  24. package/dist/calendar/ShowTimeCard/ShowTimeCard.spec.js +241 -0
  25. package/dist/components/Layout/Section.spec.d.ts +2 -0
  26. package/dist/components/Layout/Section.spec.d.ts.map +1 -0
  27. package/dist/components/Layout/Section.spec.js +149 -0
  28. package/dist/components/Layout/Sidebar.spec.d.ts +2 -0
  29. package/dist/components/Layout/Sidebar.spec.d.ts.map +1 -0
  30. package/dist/components/Layout/Sidebar.spec.js +186 -0
  31. package/dist/components/Layout/Stack.spec.js +3 -3
  32. package/dist/constants/formOptions.spec.js +9 -5
  33. package/dist/datetime/__tests__/format.test.js +1 -1
  34. package/dist/datetime/__tests__/parse.test.js +1 -1
  35. package/dist/datetime/__tests__/timezone.test.js +124 -2
  36. package/dist/datetime/parse.js +1 -1
  37. package/dist/forms/createFieldTracker.spec.d.ts +2 -0
  38. package/dist/forms/createFieldTracker.spec.d.ts.map +1 -0
  39. package/dist/forms/createFieldTracker.spec.js +343 -0
  40. package/dist/forms/createFormStore.spec.d.ts +2 -0
  41. package/dist/forms/createFormStore.spec.d.ts.map +1 -0
  42. package/dist/forms/createFormStore.spec.js +689 -0
  43. package/dist/forms/createFormStore.svelte.js +0 -1
  44. package/dist/index.d.ts +4 -112
  45. package/dist/index.js +4 -190
  46. package/dist/patterns/data/DataGrid.spec.d.ts +2 -0
  47. package/dist/patterns/data/DataGrid.spec.d.ts.map +1 -0
  48. package/dist/patterns/data/DataGrid.spec.js +159 -0
  49. package/dist/patterns/data/DataList.spec.d.ts +2 -0
  50. package/dist/patterns/data/DataList.spec.d.ts.map +1 -0
  51. package/dist/patterns/data/DataList.spec.js +158 -0
  52. package/dist/patterns/data/DataTable.spec.d.ts +2 -0
  53. package/dist/patterns/data/DataTable.spec.d.ts.map +1 -0
  54. package/dist/patterns/data/DataTable.spec.js +196 -0
  55. package/dist/patterns/forms/FormActions.spec.js +10 -3
  56. package/dist/patterns/forms/FormGrid.spec.d.ts +2 -0
  57. package/dist/patterns/forms/FormGrid.spec.d.ts.map +1 -0
  58. package/dist/patterns/forms/FormGrid.spec.js +125 -0
  59. package/dist/patterns/forms/FormSection.spec.d.ts +2 -0
  60. package/dist/patterns/forms/FormSection.spec.d.ts.map +1 -0
  61. package/dist/patterns/forms/FormSection.spec.js +153 -0
  62. package/dist/patterns/layout/Sidebar.spec.d.ts +2 -0
  63. package/dist/patterns/layout/Sidebar.spec.d.ts.map +1 -0
  64. package/dist/patterns/layout/Sidebar.spec.js +159 -0
  65. package/dist/patterns/navigation/BottomNav.svelte +4 -4
  66. package/dist/patterns/navigation/Header.spec.js +33 -24
  67. package/dist/patterns/page/PageHeader.spec.d.ts +2 -0
  68. package/dist/patterns/page/PageHeader.spec.d.ts.map +1 -0
  69. package/dist/patterns/page/PageHeader.spec.js +167 -0
  70. package/dist/patterns/page/PageLayout.spec.d.ts +2 -0
  71. package/dist/patterns/page/PageLayout.spec.d.ts.map +1 -0
  72. package/dist/patterns/page/PageLayout.spec.js +145 -0
  73. package/dist/patterns/page/PageLoader.spec.js +5 -2
  74. package/dist/patterns/page/SectionHeader.spec.d.ts +2 -0
  75. package/dist/patterns/page/SectionHeader.spec.d.ts.map +1 -0
  76. package/dist/patterns/page/SectionHeader.spec.js +197 -0
  77. package/dist/presets/badges.spec.d.ts +2 -0
  78. package/dist/presets/badges.spec.d.ts.map +1 -0
  79. package/dist/presets/badges.spec.js +172 -0
  80. package/dist/presets/buttons.spec.d.ts +2 -0
  81. package/dist/presets/buttons.spec.d.ts.map +1 -0
  82. package/dist/presets/buttons.spec.js +135 -0
  83. package/dist/primitives/Accordion/Accordion.spec.d.ts +2 -0
  84. package/dist/primitives/Accordion/Accordion.spec.d.ts.map +1 -0
  85. package/dist/primitives/Accordion/Accordion.spec.js +83 -0
  86. package/dist/primitives/Accordion/AccordionItem.spec.d.ts +2 -0
  87. package/dist/primitives/Accordion/AccordionItem.spec.d.ts.map +1 -0
  88. package/dist/primitives/Accordion/AccordionItem.spec.js +661 -0
  89. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte +107 -0
  90. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts +35 -0
  91. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte.d.ts.map +1 -0
  92. package/dist/primitives/Alert/Alert.spec.js +5 -2
  93. package/dist/primitives/Avatar/Avatar.spec.d.ts +2 -0
  94. package/dist/primitives/Avatar/Avatar.spec.d.ts.map +1 -0
  95. package/dist/primitives/Avatar/Avatar.spec.js +211 -0
  96. package/dist/primitives/Badges/Badge.spec.js +109 -68
  97. package/dist/primitives/BottomSheet/BottomSheet.spec.js +36 -27
  98. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +13 -0
  99. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +7 -0
  100. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts.map +1 -0
  101. package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +15 -13
  102. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +5 -5
  103. package/dist/primitives/Button/Button.spec.js +83 -71
  104. package/dist/primitives/Button/ButtonSaveDemo.spec.js +100 -2
  105. package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts +2 -0
  106. package/dist/primitives/Button/ButtonVariantShowcase.spec.d.ts.map +1 -0
  107. package/dist/primitives/Button/ButtonVariantShowcase.spec.js +202 -0
  108. package/dist/primitives/Card.spec.js +1 -1
  109. package/dist/primitives/Checkbox/Checkbox.spec.d.ts +2 -0
  110. package/dist/primitives/Checkbox/Checkbox.spec.d.ts.map +1 -0
  111. package/dist/primitives/Checkbox/Checkbox.spec.js +252 -0
  112. package/dist/primitives/DarkModeToggle.spec.js +84 -51
  113. package/dist/primitives/Drawer/Drawer.spec.d.ts +2 -0
  114. package/dist/primitives/Drawer/Drawer.spec.d.ts.map +1 -0
  115. package/dist/primitives/Drawer/Drawer.spec.js +212 -0
  116. package/dist/primitives/Dropdown/Dropdown.spec.d.ts +2 -0
  117. package/dist/primitives/Dropdown/Dropdown.spec.d.ts.map +1 -0
  118. package/dist/primitives/Dropdown/Dropdown.spec.js +366 -0
  119. package/dist/primitives/Dropdown/DropdownItem.spec.d.ts +2 -0
  120. package/dist/primitives/Dropdown/DropdownItem.spec.d.ts.map +1 -0
  121. package/dist/primitives/Dropdown/DropdownItem.spec.js +182 -0
  122. package/dist/primitives/Icons/iconTestUtils.spec.d.ts +2 -0
  123. package/dist/primitives/Icons/iconTestUtils.spec.d.ts.map +1 -0
  124. package/dist/primitives/Icons/iconTestUtils.spec.js +235 -0
  125. package/dist/primitives/Input/Input.spec.js +14 -14
  126. package/dist/primitives/Input/Input.svelte +1 -14
  127. package/dist/primitives/Input/Input.svelte.d.ts.map +1 -1
  128. package/dist/primitives/Input/Select.spec.js +11 -17
  129. package/dist/primitives/Input/Textarea.spec.d.ts +2 -0
  130. package/dist/primitives/Input/Textarea.spec.d.ts.map +1 -0
  131. package/dist/primitives/Input/Textarea.spec.js +255 -0
  132. package/dist/primitives/Label/Label.spec.d.ts +2 -0
  133. package/dist/primitives/Label/Label.spec.d.ts.map +1 -0
  134. package/dist/primitives/Label/Label.spec.js +157 -0
  135. package/dist/primitives/Modal/Modal.spec.js +29 -25
  136. package/dist/primitives/Modal/ModalTestWrapper.svelte +65 -0
  137. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +23 -0
  138. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts.map +1 -0
  139. package/dist/primitives/NumberInput/NumberInput.spec.d.ts +2 -0
  140. package/dist/primitives/NumberInput/NumberInput.spec.d.ts.map +1 -0
  141. package/dist/primitives/NumberInput/NumberInput.spec.js +235 -0
  142. package/dist/primitives/Pagination/Pagination.spec.d.ts +2 -0
  143. package/dist/primitives/Pagination/Pagination.spec.d.ts.map +1 -0
  144. package/dist/primitives/Pagination/Pagination.spec.js +266 -0
  145. package/dist/primitives/Radio/Radio.spec.d.ts +2 -0
  146. package/dist/primitives/Radio/Radio.spec.d.ts.map +1 -0
  147. package/dist/primitives/Radio/Radio.spec.js +206 -0
  148. package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts +2 -0
  149. package/dist/primitives/Skeleton/CardPlaceholder.spec.d.ts.map +1 -0
  150. package/dist/primitives/Skeleton/CardPlaceholder.spec.js +156 -0
  151. package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts +2 -0
  152. package/dist/primitives/Skeleton/ImagePlaceholder.spec.d.ts.map +1 -0
  153. package/dist/primitives/Skeleton/ImagePlaceholder.spec.js +120 -0
  154. package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts +2 -0
  155. package/dist/primitives/Skeleton/ListPlaceholder.spec.d.ts.map +1 -0
  156. package/dist/primitives/Skeleton/ListPlaceholder.spec.js +220 -0
  157. package/dist/primitives/Skeleton/Skeleton.spec.d.ts +2 -0
  158. package/dist/primitives/Skeleton/Skeleton.spec.d.ts.map +1 -0
  159. package/dist/primitives/Skeleton/Skeleton.spec.js +173 -0
  160. package/dist/primitives/Spinner/Spinner.spec.js +25 -29
  161. package/dist/primitives/Tabs/TabItem.spec.d.ts +2 -0
  162. package/dist/primitives/Tabs/TabItem.spec.d.ts.map +1 -0
  163. package/dist/primitives/Tabs/TabItem.spec.js +130 -0
  164. package/dist/primitives/Tabs/Tabs.spec.d.ts +2 -0
  165. package/dist/primitives/Tabs/Tabs.spec.d.ts.map +1 -0
  166. package/dist/primitives/Tabs/Tabs.spec.js +295 -0
  167. package/dist/primitives/Tabs/TabsWithItems.test.svelte +18 -0
  168. package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts +16 -0
  169. package/dist/primitives/Tabs/TabsWithItems.test.svelte.d.ts.map +1 -0
  170. package/dist/primitives/Toggle.spec.js +93 -77
  171. package/dist/primitives/Typography/Typography.spec.d.ts +2 -0
  172. package/dist/primitives/Typography/Typography.spec.d.ts.map +1 -0
  173. package/dist/primitives/Typography/Typography.spec.js +183 -0
  174. package/dist/primitives/ValidationError.spec.js +1 -1
  175. package/dist/primitives/index.d.ts +1 -0
  176. package/dist/primitives/index.js +3 -0
  177. package/dist/recipes/CropImage/CropImage.spec.js +1 -9
  178. package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts +2 -0
  179. package/dist/recipes/ImageUploader/ImageUploader.spec.d.ts.map +1 -0
  180. package/dist/recipes/ImageUploader/ImageUploader.spec.js +1351 -0
  181. package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts +2 -0
  182. package/dist/recipes/SuperLogin/SuperLogin.spec.d.ts.map +1 -0
  183. package/dist/recipes/SuperLogin/SuperLogin.spec.js +1436 -0
  184. package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts +2 -0
  185. package/dist/recipes/feedback/EmptyState/EmptyState.spec.d.ts.map +1 -0
  186. package/dist/recipes/feedback/EmptyState/EmptyState.spec.js +202 -0
  187. package/dist/recipes/feedback/ErrorDisplay.spec.js +6 -6
  188. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.spec.js +21 -17
  189. package/dist/recipes/fields/CheckboxField.spec.d.ts +2 -0
  190. package/dist/recipes/fields/CheckboxField.spec.d.ts.map +1 -0
  191. package/dist/recipes/fields/CheckboxField.spec.js +135 -0
  192. package/dist/recipes/fields/FormField.spec.d.ts +2 -0
  193. package/dist/recipes/fields/FormField.spec.d.ts.map +1 -0
  194. package/dist/recipes/fields/FormField.spec.js +159 -0
  195. package/dist/recipes/fields/RadioGroup.spec.d.ts +2 -0
  196. package/dist/recipes/fields/RadioGroup.spec.d.ts.map +1 -0
  197. package/dist/recipes/fields/RadioGroup.spec.js +199 -0
  198. package/dist/recipes/fields/SelectField.spec.d.ts +2 -0
  199. package/dist/recipes/fields/SelectField.spec.d.ts.map +1 -0
  200. package/dist/recipes/fields/SelectField.spec.js +188 -0
  201. package/dist/recipes/fields/TextareaField.spec.d.ts +2 -0
  202. package/dist/recipes/fields/TextareaField.spec.d.ts.map +1 -0
  203. package/dist/recipes/fields/TextareaField.spec.js +205 -0
  204. package/dist/recipes/fields/ToggleField.spec.d.ts +2 -0
  205. package/dist/recipes/fields/ToggleField.spec.d.ts.map +1 -0
  206. package/dist/recipes/fields/ToggleField.spec.js +153 -0
  207. package/dist/recipes/inputs/MultiSelect.spec.js +4 -3
  208. package/dist/recipes/inputs/MultiSelect.svelte +10 -3
  209. package/dist/recipes/inputs/MultiSelect.svelte.d.ts +2 -0
  210. package/dist/recipes/inputs/MultiSelect.svelte.d.ts.map +1 -1
  211. package/dist/recipes/inputs/OTPInput.spec.js +52 -39
  212. package/dist/recipes/inputs/PasswordInput.spec.d.ts +2 -0
  213. package/dist/recipes/inputs/PasswordInput.spec.d.ts.map +1 -0
  214. package/dist/recipes/inputs/PasswordInput.spec.js +410 -0
  215. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.spec.js +253 -173
  216. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte +71 -0
  217. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts +9 -0
  218. package/dist/recipes/inputs/PasswordStrengthIndicator/TestWrapper.svelte.d.ts.map +1 -0
  219. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.spec.js +1246 -300
  220. package/dist/recipes/inputs/Search.spec.d.ts +2 -0
  221. package/dist/recipes/inputs/Search.spec.d.ts.map +1 -0
  222. package/dist/recipes/inputs/Search.spec.js +177 -0
  223. package/dist/recipes/inputs/SelectDropdown.spec.d.ts +2 -0
  224. package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +1 -0
  225. package/dist/recipes/inputs/SelectDropdown.spec.js +512 -0
  226. package/dist/recipes/modals/AlertModal.spec.d.ts +2 -0
  227. package/dist/recipes/modals/AlertModal.spec.d.ts.map +1 -0
  228. package/dist/recipes/modals/AlertModal.spec.js +432 -0
  229. package/dist/recipes/modals/ConfirmationModal.spec.js +36 -21
  230. package/dist/recipes/modals/InputModal.spec.d.ts +2 -0
  231. package/dist/recipes/modals/InputModal.spec.d.ts.map +1 -0
  232. package/dist/recipes/modals/InputModal.spec.js +872 -0
  233. package/dist/recipes/modals/ModalTestWrapper.spec.d.ts +2 -0
  234. package/dist/recipes/modals/ModalTestWrapper.spec.d.ts.map +1 -0
  235. package/dist/recipes/modals/ModalTestWrapper.spec.js +502 -0
  236. package/dist/recipes/modals/StatusModal.spec.d.ts +2 -0
  237. package/dist/recipes/modals/StatusModal.spec.d.ts.map +1 -0
  238. package/dist/recipes/modals/StatusModal.spec.js +599 -0
  239. package/dist/services/ShowService.spec.js +18 -15
  240. package/dist/stories/ButtonAuditDashboard.spec.d.ts +2 -0
  241. package/dist/stories/ButtonAuditDashboard.spec.d.ts.map +1 -0
  242. package/dist/stories/ButtonAuditDashboard.spec.js +913 -0
  243. package/dist/stories/ButtonAuditReview.spec.d.ts +2 -0
  244. package/dist/stories/ButtonAuditReview.spec.d.ts.map +1 -0
  245. package/dist/stories/ButtonAuditReview.spec.js +422 -0
  246. package/dist/stories/ButtonGridView.spec.d.ts +2 -0
  247. package/dist/stories/ButtonGridView.spec.d.ts.map +1 -0
  248. package/dist/stories/ButtonGridView.spec.js +667 -0
  249. package/dist/stories/ButtonShowcase.spec.d.ts +2 -0
  250. package/dist/stories/ButtonShowcase.spec.d.ts.map +1 -0
  251. package/dist/stories/ButtonShowcase.spec.js +499 -0
  252. package/dist/stories/PatternsGallery.spec.d.ts +2 -0
  253. package/dist/stories/PatternsGallery.spec.d.ts.map +1 -0
  254. package/dist/stories/PatternsGallery.spec.js +514 -0
  255. package/dist/stories/PrimitivesGallery.spec.d.ts +2 -0
  256. package/dist/stories/PrimitivesGallery.spec.d.ts.map +1 -0
  257. package/dist/stories/PrimitivesGallery.spec.js +813 -0
  258. package/dist/stories/RecipesGallery.spec.d.ts +2 -0
  259. package/dist/stories/RecipesGallery.spec.d.ts.map +1 -0
  260. package/dist/stories/RecipesGallery.spec.js +299 -0
  261. package/dist/stripe/useStripeTheme.spec.d.ts +2 -0
  262. package/dist/stripe/useStripeTheme.spec.d.ts.map +1 -0
  263. package/dist/stripe/useStripeTheme.spec.js +793 -0
  264. package/dist/telemetry.d.ts.map +1 -1
  265. package/dist/telemetry.js +6 -5
  266. package/dist/telemetry.spec.js +495 -12
  267. package/dist/tokens/__tests__/colors.test.d.ts +2 -0
  268. package/dist/tokens/__tests__/colors.test.d.ts.map +1 -0
  269. package/dist/tokens/__tests__/colors.test.js +152 -0
  270. package/dist/tokens/__tests__/radius.test.d.ts +2 -0
  271. package/dist/tokens/__tests__/radius.test.d.ts.map +1 -0
  272. package/dist/tokens/__tests__/radius.test.js +118 -0
  273. package/dist/tokens/__tests__/shadows.test.d.ts +2 -0
  274. package/dist/tokens/__tests__/shadows.test.d.ts.map +1 -0
  275. package/dist/tokens/__tests__/shadows.test.js +105 -0
  276. package/dist/tokens/__tests__/spacing.test.js +11 -8
  277. package/dist/tokens/__tests__/typography.test.d.ts +2 -0
  278. package/dist/tokens/__tests__/typography.test.d.ts.map +1 -0
  279. package/dist/tokens/__tests__/typography.test.js +156 -0
  280. package/dist/tokens/__tests__/z-index.test.d.ts +2 -0
  281. package/dist/tokens/__tests__/z-index.test.d.ts.map +1 -0
  282. package/dist/tokens/__tests__/z-index.test.js +121 -0
  283. package/dist/utils/apiConfig.spec.js +102 -1
  284. package/dist/utils/formatters.spec.d.ts +2 -0
  285. package/dist/utils/formatters.spec.d.ts.map +1 -0
  286. package/dist/utils/formatters.spec.js +82 -0
  287. package/dist/utils/transitions.spec.d.ts +2 -0
  288. package/dist/utils/transitions.spec.d.ts.map +1 -0
  289. package/dist/utils/transitions.spec.js +130 -0
  290. package/package.json +8 -3
@@ -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
- }));
8
-
9
- // 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
- });
41
-
42
- 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
- })),
51
- }));
52
-
53
- describe('PlaceAutocomplete', () => {
54
- let PlaceAutocomplete;
55
-
56
- beforeAll(async () => {
57
- vi.resetModules();
58
- PlaceAutocomplete = (await import('./PlaceAutocomplete.svelte')).default;
59
- }, 60000);
60
-
61
- beforeEach(() => {
62
- vi.clearAllMocks();
63
- });
64
-
65
- afterEach(() => {
66
- cleanup();
67
- });
68
-
69
- describe('Basic Rendering', () => {
70
- it('renders the component', async () => {
71
- const { container } = render(PlaceAutocomplete);
72
- expect(container.querySelector('.place-wrapper')).toBeDefined();
73
- });
74
-
75
- it('renders with default placeholder', async () => {
76
- const { container } = render(PlaceAutocomplete);
77
- const input = container.querySelector('input');
78
- expect(input?.placeholder).toContain('Search');
79
- });
80
-
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');
87
- });
88
-
89
- it('renders search icon', async () => {
90
- const { container } = render(PlaceAutocomplete);
91
- expect(container.querySelector('svg')).toBeDefined();
92
- });
93
- });
94
-
95
- describe('Props', () => {
96
- it('accepts initialValue prop', async () => {
97
- const { container } = render(PlaceAutocomplete, {
98
- props: { initialValue: 'New York, NY' },
99
- });
100
- const input = container.querySelector('input');
101
- expect(input?.value).toBe('New York, NY');
102
- });
103
-
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);
110
- });
111
-
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');
118
- });
119
-
120
- it('accepts animateFocus prop', async () => {
121
- const { container } = render(PlaceAutocomplete, {
122
- props: { animateFocus: false },
123
- });
124
- expect(container.querySelector('.place-animate-focus')).toBeNull();
125
- });
126
-
127
- it('accepts autocomplete prop', async () => {
128
- const { container } = render(PlaceAutocomplete, {
129
- props: { autocomplete: 'off' },
130
- });
131
- const input = container.querySelector('input');
132
- expect(input?.autocomplete).toBe('off');
133
- });
134
- });
135
-
136
- describe('Input Interaction', () => {
137
- it('updates value on input', async () => {
138
- const { container } = render(PlaceAutocomplete);
139
- const input = container.querySelector('input');
140
-
141
- await fireEvent.input(input, { target: { value: 'Los' } });
142
- expect(input?.value).toBe('Los');
143
- });
144
-
145
- it('clears results on empty input', async () => {
146
- const { container } = render(PlaceAutocomplete);
147
- const input = container.querySelector('input');
148
-
149
- await fireEvent.input(input, { target: { value: 'Los' } });
150
- await fireEvent.input(input, { target: { value: '' } });
151
- expect(input?.value).toBe('');
152
- });
153
- });
154
-
155
- describe('Keyboard Navigation', () => {
156
- it('handles Escape key', async () => {
157
- const { container } = render(PlaceAutocomplete);
158
- const input = container.querySelector('input');
159
-
160
- await fireEvent.keyDown(input, { key: 'Escape' });
161
- expect(container).toBeDefined();
162
- });
163
-
164
- it('handles ArrowDown key with no results', async () => {
165
- const { container } = render(PlaceAutocomplete);
166
- const input = container.querySelector('input');
167
-
168
- await fireEvent.keyDown(input, { key: 'ArrowDown' });
169
- expect(container).toBeDefined();
170
- });
171
-
172
- it('handles ArrowUp key with no results', async () => {
173
- const { container } = render(PlaceAutocomplete);
174
- const input = container.querySelector('input');
175
-
176
- await fireEvent.keyDown(input, { key: 'ArrowUp' });
177
- expect(container).toBeDefined();
178
- });
179
-
180
- it('handles Enter key with no selection', async () => {
181
- const { container } = render(PlaceAutocomplete);
182
- const input = container.querySelector('input');
183
-
184
- await fireEvent.keyDown(input, { key: 'Enter' });
185
- expect(container).toBeDefined();
186
- });
187
- });
188
-
189
- describe('Callbacks', () => {
190
- it('calls onResponse when provided', async () => {
191
- const onResponse = vi.fn();
192
- const { container } = render(PlaceAutocomplete, {
193
- props: { onResponse },
194
- });
195
- expect(container).toBeDefined();
196
- });
197
-
198
- it('calls onError when provided', async () => {
199
- const onError = vi.fn();
200
- const { container } = render(PlaceAutocomplete, {
201
- props: { onError },
202
- });
203
- expect(container).toBeDefined();
204
- });
205
- });
206
-
207
- describe('Styling', () => {
208
- it('has correct wrapper class', async () => {
209
- const { container } = render(PlaceAutocomplete);
210
- expect(container.querySelector('.place-wrapper')).toBeDefined();
211
- });
212
-
213
- it('has correct container class', async () => {
214
- const { container } = render(PlaceAutocomplete);
215
- expect(container.querySelector('.place-container')).toBeDefined();
216
- });
217
-
218
- it('has correct input class', async () => {
219
- const { container } = render(PlaceAutocomplete);
220
- expect(container.querySelector('.place-input')).toBeDefined();
221
- });
222
-
223
- it('applies animate focus class when enabled', async () => {
224
- const { container } = render(PlaceAutocomplete, {
225
- props: { animateFocus: true, disabled: false },
226
- });
227
- expect(container.querySelector('.place-animate-focus')).toBeDefined();
228
- });
229
-
230
- it('does not apply animate focus class when disabled', async () => {
231
- const { container } = render(PlaceAutocomplete, {
232
- props: { animateFocus: true, disabled: true },
233
- });
234
- expect(container.querySelector('.place-animate-focus')).toBeNull();
235
- });
236
- });
237
-
238
- describe('Mode: cityState', () => {
239
- it('filters results to only localities', async () => {
240
- const { container } = render(PlaceAutocomplete, {
241
- props: { mode: 'cityState' },
242
- });
243
- expect(container).toBeDefined();
244
- });
245
-
246
- it('updates placeholder for cityState mode', async () => {
247
- const { container } = render(PlaceAutocomplete, {
248
- props: { mode: 'cityState' },
249
- });
250
- const input = container.querySelector('input');
251
- expect(input?.placeholder).toContain('city, state');
252
- });
253
- });
254
-
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 },
260
- });
261
- expect(container).toBeDefined();
262
- });
263
- });
264
-
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
-
272
- await waitFor(() => {
273
- expect(container.querySelector('.place-dropdown')).toBeNull();
274
- });
275
- });
276
- });
277
-
278
- describe('Initial Value', () => {
279
- it('sets initial value on mount', async () => {
280
- const { container } = render(PlaceAutocomplete, {
281
- props: { initialValue: 'San Francisco, CA' },
282
- });
283
-
284
- const input = container.querySelector('input');
285
- expect(input?.value).toBe('San Francisco, CA');
286
- });
287
-
288
- it('initializes only once', async () => {
289
- const { container, rerender } = render(PlaceAutocomplete, {
290
- props: { initialValue: 'San Francisco, CA' },
291
- });
292
-
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();
298
- });
299
- });
300
- });
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';
5
+
6
+ // Mock Google Maps API
7
+ const mockAutocompleteSuggestion = {
8
+ fetchAutocompleteSuggestions: vi.fn(),
9
+ };
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
21
+ vi.mock('@googlemaps/js-api-loader', () => ({
22
+ Loader: vi.fn().mockImplementation(() => mockLoader),
23
+ }));
24
+
25
+ // Mock config
26
+ vi.mock('$lib/config.js', () => ({
27
+ PUBLIC_GOOGLE_MAPS_API_KEY: 'test-api-key',
28
+ }));
29
+
30
+ function setupTest(props = {}) {
31
+ const user = userEvent.setup();
32
+ const result = render(PlaceAutocomplete, { props });
33
+ return { user, container: result.container };
34
+ }
35
+
36
+ describe('PlaceAutocomplete Component', () => {
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ mockAutocompleteSuggestion.fetchAutocompleteSuggestions.mockResolvedValue({
40
+ suggestions: [],
41
+ });
42
+ });
43
+
44
+ afterEach(() => {
45
+ vi.clearAllMocks();
46
+ });
47
+
48
+ describe('Rendering', () => {
49
+ test('renders input element', () => {
50
+ setupTest();
51
+ const input = screen.getByRole('textbox');
52
+ expect(input).toBeInTheDocument();
53
+ });
54
+
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
+ });
61
+ });
62
+
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();
79
+ });
80
+
81
+ test('input has correct name attribute', () => {
82
+ setupTest();
83
+ const input = screen.getByRole('textbox');
84
+ expect(input).toHaveAttribute('name', 'location');
85
+ });
86
+ });
87
+
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');
94
+ });
95
+ });
96
+
97
+ test('does not display value when initialValue is empty', () => {
98
+ setupTest({ initialValue: '' });
99
+ const input = screen.getByRole('textbox');
100
+ expect(input.value).toBe('');
101
+ });
102
+ });
103
+
104
+ describe('Disabled State', () => {
105
+ test('input can be disabled', () => {
106
+ setupTest({ disabled: true });
107
+ const input = screen.getByRole('textbox');
108
+ expect(input).toBeDisabled();
109
+ });
110
+
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...');
150
+ });
151
+
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...');
157
+ });
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('');
196
+ });
197
+ });
198
+
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
+ ];
215
+
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
+ });
280
+ });
281
+
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');
301
+
302
+ await waitFor(() => {
303
+ const button = screen.getByText('Los Angeles, CA');
304
+ expect(button).toBeInTheDocument();
305
+ });
306
+ });
307
+ });
308
+
309
+ describe('Keyboard Navigation', () => {
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
+ ];
340
+
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
+ });
362
+ });
363
+
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' });
378
+
379
+ // Should cycle back to first
380
+ await fireEvent.keyDown(input, { key: 'ArrowDown' });
381
+
382
+ const highlighted = container.querySelectorAll('.bg-gray-100');
383
+ expect(highlighted.length).toBeGreaterThan(0);
384
+ });
385
+
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');
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' });
415
+ await fireEvent.keyDown(input, { key: 'ArrowUp' });
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
+ });
441
+ });
442
+
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
+ });
453
+
454
+ await fireEvent.keyDown(input, { key: 'Enter' });
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();
486
+ });
487
+ });
488
+
489
+ describe('Place Selection', () => {
490
+ test('clicking suggestion calls onResponse', async () => {
491
+ const onResponse = vi.fn();
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
+ );
526
+ });
527
+ });
528
+
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 () => {
705
+ const onError = vi.fn();
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'));
717
+ });
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();
765
+ });
766
+ });
767
+
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);
795
+ });
796
+
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);
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);
873
+
874
+ await waitFor(() => {
875
+ expect(mockPlace.fetchFields).toHaveBeenCalledWith({
876
+ fields: ['location', 'displayName'],
877
+ });
878
+ });
879
+ });
880
+ });
881
+
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
+ );
896
+ });
897
+ });
898
+
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
+ );
912
+ });
913
+ });
914
+ });
915
+
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');
977
+ });
978
+ });
979
+
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');
1003
+ });
1004
+ });
1005
+ });
1006
+
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();
1085
+ });
1086
+ });
1087
+ });
1088
+
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
+
1133
+ await waitFor(() => {
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();
1178
+ });
1179
+ });
1180
+ });
1181
+
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');
1211
+ });
1212
+ });
1213
+
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,
1232
+ });
1233
+
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
+ });
1244
+ });
1245
+ });
1246
+ });